diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,15 @@
 # Revision history for warp-quic
 
+## 0.0.5
+
+A QUIC server is stopped through `settingsInstallShutdownHandler`, as a TCP
+one is, and lets its HTTP/3 connections answer what they have taken first.
+Requires quic 0.3.15 and http3 0.1.10 for these; not on Windows yet.
+
+* Event poll for accept loop.
+  [#1123](https://github.com/yesodweb/wai/pull/1123)
+* Let HTTP/3 connections finish when the server stops.
+
 ## 0.0.4
 
 * Adjusted code to fit new range of dependency constraints. [#1043](https://github.com/yesodweb/wai/pull/1043)
diff --git a/Network/Wai/Handler/WarpQUIC.hs b/Network/Wai/Handler/WarpQUIC.hs
--- a/Network/Wai/Handler/WarpQUIC.hs
+++ b/Network/Wai/Handler/WarpQUIC.hs
@@ -4,12 +4,16 @@
 -- | WAI handler for HTTP/3 based on QUIC.
 module Network.Wai.Handler.WarpQUIC where
 
+import Control.Concurrent (forkIO)
+import Control.Concurrent.STM
+import Control.Monad (void)
 import qualified Data.ByteString as BS
 import qualified Network.HQ.Server as HQ
 import qualified Network.HTTP3.Server as H3
 import Network.QUIC
 import Network.QUIC.Server as Q
-import Network.Socket (Socket)
+import qualified Control.Exception as E
+import Network.Socket (Socket, close)
 import Network.TLS (cipherID)
 import Network.Wai
 import Network.Wai.Handler.Warp hiding (run)
@@ -19,28 +23,94 @@
 type QUICSettings = ServerConfig
 
 runQUICSocket :: QUICSettings -> Settings -> Socket -> Application -> IO ()
-runQUICSocket quicsettings settings sock app =
-    withII settings $ \ii ->
-        Q.runWithSockets [sock] quicsettings $ quicApp settings app ii
+runQUICSocket quicsettings settings sock app = runQUICSockets quicsettings settings [sock] app
 
+-- | The sockets are closed when the server has stopped, as
+--   'Network.Wai.Handler.Warp.runSettingsSocket' closes the one it is given:
+--   by then the peers have been told the server is going and the ports are a
+--   successor's to take.
 runQUICSockets :: QUICSettings -> Settings -> [Socket] -> Application -> IO ()
 runQUICSockets quicsettings settings ss app =
-    withII settings $ \ii ->
-        Q.runWithSockets ss quicsettings $ quicApp settings app ii
+    ( do
+        drn <- newDraining
+        withII settings $ \ii ->
+            Q.runWithSockets ss (stoppableBy settings drn quicsettings) $
+                quicApp settings drn app ii
+    )
+        `E.finally` mapM_ close ss
 
 -- | Running warp with HTTP/3 on QUIC.
 runQUIC :: QUICSettings -> Settings -> Application -> IO ()
-runQUIC quicsettings settings app =
+runQUIC quicsettings settings app = do
+    drn <- newDraining
     withII settings $ \ii ->
-        Q.run quicsettings $ quicApp settings app ii
+        Q.run (stoppableBy settings drn quicsettings) $ quicApp settings drn app ii
 
+-- | Whether the server is stopping, and how many HTTP/3 connections are
+--   still answering what they have taken.
+data Draining = Draining
+    { drainingVar :: TVar Bool
+    , activeVar :: TVar Int
+    }
+
+newDraining :: IO Draining
+newDraining = Draining <$> newTVarIO False <*> newTVarIO 0
+
+-- | Handing the action that stops the QUIC server to whoever installs
+--   shutdown handlers for this warp.
+--
+-- The two hooks are the same hook: each is handed the action that stops its
+-- server, once, as the server starts, and keeping it is what lets a caller
+-- stop a server that has no connection to stop it through.  So an
+-- 'Application' served over TCP and over QUIC is stopped the same way on
+-- both, by a caller that need not know which it is holding.
+--
+-- A caller that installs nothing is left as it was: the QUIC server is
+-- stopped by whatever its sockets are, being closed.
+--
+-- Stopping first tells every HTTP/3 connection to go away: GOAWAY, and the
+-- requests it has taken are answered.  The server is stopped once they are
+-- done, or once 'settingsGracefulShutdownTimeout' has passed, and closes
+-- what is left.  The wait is in a thread of its own, so that the caller,
+-- which may be stopping other servers too, is not held up.
+stoppableBy :: Settings -> Draining -> QUICSettings -> QUICSettings
+#if MIN_VERSION_quic(0,3,15)
+stoppableBy settings (Draining draining active) quicsettings =
+    quicsettings
+        { Q.scInstallShutdownHandler = settingsInstallShutdownHandler settings . drainThen
+        , Q.scCloseReason = (h3NoError, "server is closing")
+        }
+  where
+    drainThen stopServer = do
+        atomically $ writeTVar draining True
+        void $ forkIO $ do
+            late <- case settingsGracefulShutdownTimeout settings of
+                Nothing -> newTVarIO False
+                Just sec -> registerDelay $ sec * 1000000
+            atomically $
+                (readTVar active >>= check . (== 0)) `orElse` (readTVar late >>= check)
+            stopServer
+
+-- | H3_NO_ERROR (RFC 9114 Sec 8.1): the connection is being closed and
+--   nothing went wrong.  What a stopping server tells its peers, and said in
+--   the application protocol because the transport's own NO_ERROR is what a
+--   connection whose application has finished normally says.
+h3NoError :: ApplicationProtocolError
+h3NoError = ApplicationProtocolError 0x100
+#else
+-- quic before 0.3.15 has no hook to install this on, and no way to stop a
+-- server from out here at all.
+stoppableBy _ _ quicsettings = quicsettings
+#endif
+
 quicApp
     :: Settings
+    -> Draining
     -> Application
     -> InternalInfo
     -> Connection
     -> IO ()
-quicApp settings app ii conn = do
+quicApp settings drn app ii conn = do
     info <- getConnectionInfo conn
     mccc <- clientCertificateChain conn
     let addr = remoteSockAddr info
@@ -62,13 +132,22 @@
                 , H3.confTimeoutManager = timmgr
                 }
 #endif
+        server label = http2server label settings ii transport addr app
     case malpn of
         Nothing -> return ()
-        Just appProto -> do
-            let runX
-                    | "h3" `BS.isPrefixOf` appProto = H3.run
-                    | otherwise = HQ.run
-                label
-                    | "h3" `BS.isPrefixOf` appProto = "Warp HTTP/3"
-                    | otherwise = "Warp HQ"
-            runX conn conf $ http2server label settings ii transport addr app
+        Just appProto
+            | "h3" `BS.isPrefixOf` appProto ->
+                -- Counted while it answers, so that stopping waits for it.
+                E.bracket_ (counting 1) (counting (-1)) $
+                    runH3 conn conf $ server "Warp HTTP/3"
+            -- HTTP/0.9 has no GOAWAY: it is closed when the server stops.
+            | otherwise -> HQ.run conn conf $ server "Warp HQ"
+  where
+    counting n = atomically $ modifyTVar' (activeVar drn) (+ n)
+#if MIN_VERSION_http3(0,1,10)
+    -- GOAWAY once the server is stopping, then the requests taken are
+    -- answered and the connection ends.
+    runH3 = H3.runWith $ readTVar (drainingVar drn) >>= check
+#else
+    runH3 = H3.run
+#endif
diff --git a/warp-quic.cabal b/warp-quic.cabal
--- a/warp-quic.cabal
+++ b/warp-quic.cabal
@@ -1,6 +1,6 @@
 cabal-version:      >=1.10
 name:               warp-quic
-version:            0.0.4
+version:            0.0.5
 license:            BSD3
 license-file:       LICENSE
 maintainer:         Kazu Yamamoto <kazu@iij.ad.jp>
@@ -22,6 +22,7 @@
         http3 <0.2,
         network,
         quic >=0.2 && <0.4,
+        stm,
         tls >=0.8 && < 3,
         wai,
         warp >=3.4.4
