diff --git a/NEWS.md b/NEWS.md
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,6 +1,12 @@
 Changes
 =======
 
+0.4.0 (29 Sep 2023)
+-------------------
+
+  - update to work with ghc-9.6; thank you, @tfausak
+  - bump min base version to 4.18
+
 0.3.0 (03 Feb 2020)
 -------------------
 
diff --git a/daemons.cabal b/daemons.cabal
--- a/daemons.cabal
+++ b/daemons.cabal
@@ -1,6 +1,6 @@
 Name:           daemons
-Version:        0.3.0
-Cabal-Version:  >= 1.8
+Version:        0.4.0
+Cabal-Version:  1.24
 License:        GPL-3
 License-File:   LICENSE
 Stability:      experimental
@@ -37,7 +37,7 @@
 
 Library
   Hs-Source-Dirs:       src
-  Build-depends:        base >= 4 && < 5,
+  Build-depends:        base >= 4.18 && < 5,
                         bytestring,
                         cereal >= 0.4.0,
                         data-default,
@@ -55,36 +55,42 @@
                         System.Daemon,
                         System.Posix.Daemon
   Other-modules:
+  Default-language:     Haskell2010
 
 Executable memo
-  Build-depends:        base >= 4 && < 5, bytestring, cereal, containers,
+  Build-depends:        base >= 4.18 && < 5, bytestring, cereal, containers,
                         daemons, data-default, ghc-prim
   Main-Is:              examples/Memo.hs
   Ghc-options:          -Wall
+  Default-language:     Haskell2010
 
 Executable addone
-  Build-depends:        base >= 4 && < 5, daemons, data-default, ghc-prim
+  Build-depends:        base >= 4.18 && < 5, daemons, data-default, ghc-prim
   Main-Is:              examples/AddOne.hs
   Ghc-options:          -Wall
+  Default-language:     Haskell2010
 
 Executable queue
-  Build-depends:        base >= 4 && < 5, bytestring, cereal, containers,
+  Build-depends:        base >= 4.18 && < 5, bytestring, cereal, containers,
                         daemons, data-default, ghc-prim, network,
                         pipes >= 4.0.0, transformers
   Main-Is:              examples/Queue.hs
   Ghc-options:          -Wall
+  Default-language:     Haskell2010
 
 Executable name
-  Build-depends:        base >= 4 && < 5, bytestring, cereal, containers,
+  Build-depends:        base >= 4.18 && < 5, bytestring, cereal, containers,
                         daemons, data-default, ghc-prim
   Main-Is:              examples/Name.hs
   Ghc-options:          -Wall
+  Default-language:     Haskell2010
 
 Test-suite daemon
   Hs-Source-Dirs:       test
   Main-Is:              Daemon.hs
   Type:                 exitcode-stdio-1.0
-  Build-Depends:        base >= 4 && < 5, daemons, data-default, directory,
+  Build-Depends:        base >= 4.18 && < 5, daemons, data-default, directory,
                         ghc-prim, HUnit, test-framework, test-framework-hunit,
                         unix
   Ghc-Options:          -Wall
+  Default-language:     Haskell2010
diff --git a/src/Control/Pipe/Serialize.hs b/src/Control/Pipe/Serialize.hs
--- a/src/Control/Pipe/Serialize.hs
+++ b/src/Control/Pipe/Serialize.hs
@@ -26,7 +26,6 @@
                       , Result(..), runGetPartial )
 import Pipes ( Pipe, await, yield )
 import Control.Monad ( forever )
-import Control.Monad.Fail ( MonadFail )
 
 -- | De-serialize data from strict 'ByteString's.  Uses @cereal@'s
 -- incremental 'Data.Serialize.Get' parser.
diff --git a/src/System/Posix/Daemon.hs b/src/System/Posix/Daemon.hs
--- a/src/System/Posix/Daemon.hs
+++ b/src/System/Posix/Daemon.hs
@@ -57,6 +57,7 @@
 import Prelude hiding ( FilePath )
 
 import Control.Monad ( when )
+import Data.ByteString.Char8 ( unpack )
 import Data.Default ( Default(..) )
 import System.Directory ( doesFileExist )
 import System.FilePath ( FilePath )
@@ -64,8 +65,9 @@
 import System.Posix.Files ( stdFileMode )
 import System.Posix.IO ( openFd, OpenMode(..), defaultFileFlags, closeFd
                        , dupTo, stdInput, stdOutput, stdError, getLock
-                       , createFile, fdWrite, fdRead
-                       , LockRequest (..), setLock, waitToSetLock )
+                       , createFile, fdWrite
+                       , LockRequest (..), setLock, waitToSetLock, creat )
+import System.Posix.IO.ByteString ( fdRead )
 import System.Posix.Process ( getProcessID, forkProcess, createSession )
 import System.Posix.Signals ( Signal, signalProcess, sigQUIT, sigKILL )
 
@@ -125,14 +127,14 @@
     -- Remap the standard channels based on the @redirection@
     -- parameter.
     remapFds = do
-        devnull <- openFd "/dev/null" ReadOnly Nothing defaultFileFlags
+        devnull <- openFd "/dev/null" ReadOnly defaultFileFlags
         ignore (dupTo devnull stdInput)
         closeFd devnull
 
         let file = case redirection of
                      DevNull         -> "/dev/null"
                      ToFile filepath -> filepath
-        fd <- openFd file ReadWrite (Just stdFileMode) defaultFileFlags
+        fd <- openFd file ReadWrite defaultFileFlags { creat = Just stdFileMode }
         hFlush stdout
         mapM_ (dupTo fd) [stdOutput, stdError]
         closeFd fd
@@ -165,10 +167,10 @@
     dfe <- doesFileExist pidFile
     if dfe
       then do
-          fd <- openFd pidFile ReadWrite Nothing defaultFileFlags
+          fd <- openFd pidFile ReadWrite defaultFileFlags
           -- is there an *incompatible* lock on the pidfile?
           ml <- getLock fd (WriteLock, AbsoluteSeek, 0, 0)
-          (pid, _) <- fdRead fd 100
+          pid <- fdRead fd 100 >>= \x -> return (unpack x)
           closeFd fd
           case ml of
             Nothing -> do
@@ -188,7 +190,7 @@
 killAndWait :: FilePath -> IO ()
 killAndWait pidFile = do
     signalProcessByFilePath sigQUIT pidFile
-    fd <- openFd pidFile ReadWrite Nothing defaultFileFlags
+    fd <- openFd pidFile ReadWrite defaultFileFlags
     waitToSetLock fd (WriteLock, AbsoluteSeek, 0, 0)
     closeFd fd
 
diff --git a/test/Daemon.hs b/test/Daemon.hs
--- a/test/Daemon.hs
+++ b/test/Daemon.hs
@@ -6,7 +6,6 @@
 import Control.Exception
 import Control.Monad
 import Data.Default
-import Data.Monoid
 import System.Directory
 import System.Posix.Daemon
 import System.Posix.Process
