diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,3 +1,8 @@
+## 0.2.4.0
+
+* Add `readProcessInterleaved` and `readProcessInterleaved_` to support
+  capturing output from stdout and stderr in a single ByteString value.
+
 ## 0.2.3.0
 
 * Add support for the single-threaded runtime via polling
diff --git a/src/System/Process/Typed.hs b/src/System/Process/Typed.hs
--- a/src/System/Process/Typed.hs
+++ b/src/System/Process/Typed.hs
@@ -65,6 +65,8 @@
     , readProcessStdout_
     , readProcessStderr
     , readProcessStderr_
+    , readProcessInterleaved
+    , readProcessInterleaved_
 
       -- * Interact with a process
 
@@ -534,7 +536,18 @@
 --
 -- @since 0.1.0.0
 byteStringOutput :: StreamSpec 'STOutput (STM L.ByteString)
-byteStringOutput = mkStreamSpec P.CreatePipe $ \pc (Just h) -> do
+byteStringOutput = mkStreamSpec P.CreatePipe $ \pc (Just h) -> byteStringFromHandle pc h
+
+-- | Helper function (not exposed) for both 'byteStringOutput' and
+-- 'withProcessInterleave'. This will consume all of the output from
+-- the given 'Handle' in a separate thread and provide access to the
+-- resulting 'L.ByteString' via STM. Second action will close the
+-- reader handle.
+byteStringFromHandle
+  :: ProcessConfig () () ()
+  -> Handle -- ^ reader handle
+  -> IO (STM L.ByteString, IO ())
+byteStringFromHandle pc h = do
     mvar <- newEmptyTMVarIO
 
     void $ async $ do
@@ -799,12 +812,13 @@
   where
     pc' = setStdout byteStringOutput pc
 
--- | Same as 'readProcess', but only read the stderr of the process. Original settings for stderr remain.
+-- | Same as 'readProcess', but only read the stderr of the process.
+-- Original settings for stdout remain.
 --
 -- @since 0.2.1.0
 readProcessStderr
   :: MonadIO m
-  => ProcessConfig stdin stderrIgnored stderr
+  => ProcessConfig stdin stdout stderrIgnored
   -> m (ExitCode, L.ByteString)
 readProcessStderr pc =
     liftIO $ withProcess pc' $ \p -> atomically $ (,)
@@ -819,7 +833,7 @@
 -- @since 0.2.1.0
 readProcessStderr_
   :: MonadIO m
-  => ProcessConfig stdin stderrIgnored stderr
+  => ProcessConfig stdin stdout stderrIgnored
   -> m L.ByteString
 readProcessStderr_ pc =
     liftIO $ withProcess pc' $ \p -> atomically $ do
@@ -830,6 +844,60 @@
         return stderr
   where
     pc' = setStderr byteStringOutput pc
+
+withProcessInterleave
+  :: ProcessConfig stdin stdoutIgnored stderrIgnored
+  -> (Process stdin (STM L.ByteString) () -> IO a)
+  -> IO a
+withProcessInterleave pc inner = do
+    -- Create a pipe to be shared for both stdout and stderr
+    (readEnd, writeEnd) <- P.createPipe
+
+    -- Use the writer end of the pipe for both stdout and stderr. For
+    -- the stdout half, use byteStringFromHandle to read the data into
+    -- a lazy ByteString in memory.
+    let pc' = setStdout (mkStreamSpec (P.UseHandle writeEnd) (\pc'' Nothing -> byteStringFromHandle pc'' readEnd))
+            $ setStderr (useHandleOpen writeEnd)
+              pc
+    withProcess pc' $ \p -> do
+      -- Now that the process is forked, close the writer end of this
+      -- pipe, otherwise the reader end will never give an EOF.
+      hClose writeEnd
+      inner p
+
+-- | Same as 'readProcess', but interleaves stderr with stdout.
+--
+-- Motivation: Use this function if you need stdout interleaved with stderr
+-- output (e.g. from an HTTP server) in order to debug failures.
+--
+-- @since 0.2.4.0
+readProcessInterleaved
+  :: MonadIO m
+  => ProcessConfig stdin stdoutIgnored stderrIgnored
+  -> m (ExitCode, L.ByteString)
+readProcessInterleaved pc =
+    liftIO $
+    withProcessInterleave pc $ \p ->
+    atomically $ (,)
+      <$> waitExitCodeSTM p
+      <*> getStdout p
+
+-- | Same as 'readProcessInterleaved', but instead of returning the 'ExitCode',
+-- checks it with 'checkExitCode'.
+--
+-- @since 0.2.4.0
+readProcessInterleaved_
+  :: MonadIO m
+  => ProcessConfig stdin stdoutIgnored stderrIgnored
+  -> m L.ByteString
+readProcessInterleaved_ pc =
+    liftIO $ do
+    withProcessInterleave pc $ \p -> atomically $ do
+      stdout' <- getStdout p
+      checkExitCodeSTM p `catchSTM` \ece -> throwSTM ece
+        { eceStdout = stdout'
+        }
+      return stdout'
 
 -- | Run the given process, wait for it to exit, and returns its
 -- 'ExitCode'.
diff --git a/test/System/Process/TypedSpec.hs b/test/System/Process/TypedSpec.hs
--- a/test/System/Process/TypedSpec.hs
+++ b/test/System/Process/TypedSpec.hs
@@ -86,3 +86,33 @@
         let encoded = S.filter (/= 10) $ L.toStrict lbs
         raw <- S.readFile fp
         encoded `shouldBe` B64.encode raw
+
+    it "interleaved output" $ withSystemTempFile "interleaved-output" $ \fp h -> do
+        S.hPut h "\necho 'stdout'\n>&2 echo 'stderr'\necho 'stdout'"
+        hClose h
+
+        let config = proc "sh" [fp]
+        -- Assert, that our bash script doesn't send output only to stdout and
+        -- we assume that we captured from stderr as well
+        onlyErr <- readProcessStderr_ (setStdout createPipe config)
+        onlyErr `shouldBe` "stderr\n"
+
+        (res, lbs1) <- readProcessInterleaved config
+        res `shouldBe` ExitSuccess
+        lbs1 `shouldBe` "stdout\nstderr\nstdout\n"
+
+        lbs2 <- readProcessInterleaved_ config
+        lbs1 `shouldBe` lbs2
+
+    it "interleaved output handles large data" $ withSystemTempFile "interleaved-output" $ \fp h -> do
+        S.hPut h "\nfor i in {1..4064}; do\necho 'stdout';\n>&2 echo 'stderr';\necho 'stdout';\ndone"
+        hClose h
+
+        let config = proc "sh" [fp]
+        (result, lbs1) <- readProcessInterleaved config
+        result `shouldBe` ExitSuccess
+        lbs2 <- readProcessInterleaved_ config
+        lbs1 `shouldBe` lbs2
+
+        let expected = "stdout\nstderr\nstdout\n"
+        L.take (L.length expected) lbs1 `shouldBe` expected
diff --git a/typed-process.cabal b/typed-process.cabal
--- a/typed-process.cabal
+++ b/typed-process.cabal
@@ -1,13 +1,13 @@
-cabal-version: >= 1.10
+cabal-version: 1.12
 
--- This file has been generated from package.yaml by hpack version 0.29.0.
+-- This file has been generated from package.yaml by hpack version 0.31.1.
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: 8fd30ba42322fffadc01326bcc1370fa86c650057800ca91d5381d89e91df797
+-- hash: 11c70077cb1b56f53730fd5ab768dd6b89dd6c3850649afb4cae269796982aff
 
 name:           typed-process
-version:        0.2.3.0
+version:        0.2.4.0
 synopsis:       Run external processes, with strong typing of streams
 description:    Please see the tutorial at <https://haskell-lang.org/library/typed-process>
 category:       System
@@ -19,8 +19,8 @@
 license-file:   LICENSE
 build-type:     Simple
 extra-source-files:
-    ChangeLog.md
     README.md
+    ChangeLog.md
 
 source-repository head
   type: git
