diff --git a/Network/Wai/Middleware/Static.hs b/Network/Wai/Middleware/Static.hs
--- a/Network/Wai/Middleware/Static.hs
+++ b/Network/Wai/Middleware/Static.hs
@@ -27,27 +27,22 @@
     ) where
 
 import Caching.ExpiringCacheMap.HashECM (newECMIO, lookupECM, CacheSettings(..), consistentDuration)
-import Control.Monad.Trans (liftIO)
-import Data.List
-#if !(MIN_VERSION_base(4,8,0))
-import Data.Monoid (Monoid(..))
-#endif
+import Control.Monad
+import qualified Crypto.Hash.SHA1 as SHA1
+import qualified Data.ByteString.Base16 as Base16
+import qualified Data.List as L
 #if !(MIN_VERSION_base(4,11,0))
 import Data.Semigroup (Semigroup(..))
 #endif
 import Data.Time
 import Data.Time.Clock.POSIX
-import Network.HTTP.Types (status200, status304)
-import Network.HTTP.Types.Header (RequestHeaders)
+import Network.HTTP.Types
 import Network.Mime (MimeType, defaultMimeLookup)
 import Network.Wai
 import System.Directory (doesFileExist, getModificationTime)
-#if !(MIN_VERSION_time(1,5,0))
-import System.Locale
-#endif
-import Crypto.Hash.Algorithms
-import Crypto.Hash
-import Data.ByteArray.Encoding
+-- import Crypto.Hash.Algorithms
+-- import Crypto.Hash
+-- import Data.ByteArray.Encoding
 import qualified Data.ByteString as BS
 import qualified Data.ByteString.Char8 as BSC
 import qualified Data.ByteString.Lazy as BSL
@@ -56,7 +51,7 @@
 
 -- | Take an incoming URI and optionally modify or filter it.
 --   The result will be treated as a filepath.
-newtype Policy = Policy { tryPolicy :: String -> Maybe String -- ^ Run a policy
+newtype Policy = Policy { tryPolicy :: String -> Maybe FilePath -- ^ Run a policy
                         }
 
 -- | Options for 'staticWithOptions' 'Middleware'.
@@ -140,19 +135,19 @@
 
 -- | Accept only URIs with given suffix
 hasSuffix :: String -> Policy
-hasSuffix = predicate . isSuffixOf
+hasSuffix = predicate . L.isSuffixOf
 
 -- | Accept only URIs with given prefix
 hasPrefix :: String -> Policy
-hasPrefix = predicate . isPrefixOf
+hasPrefix = predicate . L.isPrefixOf
 
 -- | Accept only URIs containing given string
 contains :: String -> Policy
-contains = predicate . isInfixOf
+contains = predicate . L.isInfixOf
 
 -- | Reject URIs containing \"..\"
 noDots :: Policy
-noDots = predicate (not . isInfixOf "..")
+noDots = predicate (not . L.isInfixOf "..")
 
 -- | Reject URIs that are absolute paths
 isNotAbsolute :: Policy
@@ -234,10 +229,15 @@
 -- this has no policies enabled by default and is hence insecure. Takes 'Options'.
 unsafeStaticPolicyWithOptions :: Options -> Policy -> Middleware
 unsafeStaticPolicyWithOptions options p app req callback =
-    maybe (app req callback)
-          (\fp ->
-               do exists <- liftIO $ doesFileExist fp
-                  if exists
+    maybe serveUpstream tryStaticFile mCandidateFile
+    where
+      serveUpstream :: IO ResponseReceived
+      serveUpstream = app req callback
+
+      tryStaticFile :: FilePath -> IO ResponseReceived
+      tryStaticFile fp = do
+          exists <- doesFileExist fp
+          if exists
                   then case cacheContainer options of
                          CacheContainerEmpty ->
                              sendFile fp []
@@ -248,9 +248,19 @@
                                 if checkNotModified fileMeta (readHeader "If-Modified-Since") (readHeader "If-None-Match")
                                 then sendNotModified fileMeta strategy
                                 else sendFile fp (computeHeaders fileMeta strategy)
-                  else app req callback)
+                  else serveUpstream
+
+      mCandidateFile :: Maybe FilePath
+      mCandidateFile =
+          guard isHeadOrGet >>
           (tryPolicy p $ T.unpack $ T.intercalate "/" $ pathInfo req)
-    where
+          where
+            method :: Method
+            method = requestMethod req
+
+            isHeadOrGet :: Bool
+            isHeadOrGet = method == methodHead || method == methodGet
+
       readHeader header =
           lookup header $ requestHeaders req
       checkNotModified fm modSince etag =
@@ -314,7 +324,7 @@
        return $ FileMeta
                 { fm_lastModified =
                       BSC.pack $ formatTime defaultTimeLocale "%a, %d-%b-%Y %X %Z" mtime
-                , fm_etag = convertToBase Base16 (hashlazy ct :: Digest SHA1)
+                , fm_etag = Base16.encode (SHA1.hashlazy ct)
                 , fm_fileName = fp
                 }
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,3 +1,3 @@
-# wai-middleware-static [![Build Status](https://travis-ci.org/scotty-web/wai-middleware-static.svg)](https://travis-ci.org/scotty-web/wai-middleware-static)
+# wai-middleware-static [![Build Status](https://github.com/scotty-web/wai-middleware-static/workflows/Haskell-CI/badge.svg)](https://github.com/scotty-web/wai-middleware-static/actions?query=workflow%3AHaskell-CI)
 
 WAI middleware that intercepts requests to static files and serves them if they exist.
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,3 +1,21 @@
+## 0.9.4 [2026.01.10]
+* Allow building with `time-1.15.*` (GHC 9.14).
+* Remove unused `containers`, `old-locale`, and `semigroups` dependencies.
+
+## 0.9.3 [2024.12.28]
+* Drop support for pre-8.0 versions of GHC.
+
+## 0.9.2 [2022.03.08]
+* Allow building with GHC 9.2.
+* Replace the `cryptonite` and `memory` dependencies with equivalent
+  functionality from `cryptohash-sha1` and `base16-bytestring`.
+
+## 0.9.1 [2021.10.31]
+* Always import `Data.List` qualified.
+
+## 0.9.0 [2020.10.01]
+* Only serve static files on `HEAD` or `GET` requests.
+
 ## 0.8.3 [2019.10.20]
 * Add `Options`, `staticWithOptions`, `staticPolicyWithOptions`, and `unsafeStaticPolicyWithOptions`.
 * Parameterize Middleware with options allowing custom file name to MIME type mapping.
diff --git a/test/Network/Wai/Middleware/StaticSpec.hs b/test/Network/Wai/Middleware/StaticSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Network/Wai/Middleware/StaticSpec.hs
@@ -0,0 +1,41 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Network.Wai.Middleware.StaticSpec (spec) where
+
+import           Test.Hspec hiding (shouldReturn)
+import           Test.Hspec.Expectations.Lifted
+import           Test.Hspec.Wai
+import           Test.Mockery.Directory
+
+import           Web.Scotty (scottyApp)
+import           Network.Wai.Test (SResponse(..))
+import           Network.HTTP.Types.Status
+
+import           Network.Wai.Middleware.Static
+
+spec :: Spec
+spec = around_ inTempDirectory $ do
+  describe "static" $ with (static `fmap` scottyApp (return ())) $ do
+    before_ (writeFile "foo.html" "some content") $ do
+      context "on HEAD" $ do
+        it "serves static file" $ do
+          request "HEAD" "/foo.html" [] "" `shouldReturn` SResponse {
+            simpleStatus = status200
+          , simpleHeaders = [("Content-Type", "text/html")]
+          , simpleBody = "some content"
+          }
+
+      context "on GET" $ do
+        it "serves static file" $ do
+          get "/foo.html" `shouldReturn` SResponse {
+            simpleStatus = status200
+          , simpleHeaders = [("Content-Type", "text/html")]
+          , simpleBody = "some content"
+          }
+
+      context "otherwise" $ do
+        it "serves upstream" $ do
+          post "/foo.html" "" `shouldReturn` SResponse {
+            simpleStatus = status404
+          , simpleHeaders = [("Content-Type", "text/html")]
+          , simpleBody = "<h1>404: File Not Found!</h1>"
+          }
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -fforce-recomp -F -pgmF hspec-discover #-}
diff --git a/wai-middleware-static.cabal b/wai-middleware-static.cabal
--- a/wai-middleware-static.cabal
+++ b/wai-middleware-static.cabal
@@ -1,5 +1,5 @@
 Name:                wai-middleware-static
-Version:             0.8.3
+Version:             0.9.4
 Synopsis:            WAI middleware that serves requests to static files.
 Homepage:            https://github.com/scotty-web/wai-middleware-static
 Bug-reports:         https://github.com/scotty-web/wai-middleware-static/issues
@@ -17,39 +17,59 @@
   if they exist.
   .
   [WAI] <http://hackage.haskell.org/package/wai>
-tested-with:         GHC == 7.6.3
-                   , GHC == 7.8.4
-                   , GHC == 7.10.3
-                   , GHC == 8.0.2
+tested-with:         GHC == 8.0.2
                    , GHC == 8.2.2
                    , GHC == 8.4.4
                    , GHC == 8.6.5
-                   , GHC == 8.8.1
+                   , GHC == 8.8.4
+                   , GHC == 8.10.7
+                   , GHC == 9.0.2
+                   , GHC == 9.2.8
+                   , GHC == 9.4.8
+                   , GHC == 9.6.7
+                   , GHC == 9.8.4
+                   , GHC == 9.10.3
+                   , GHC == 9.12.2
+                   , GHC == 9.14.1
 Extra-source-files:  changelog.md, README.md
 
 Library
   Exposed-modules:     Network.Wai.Middleware.Static
   default-language:    Haskell2010
   Build-depends:
-                       base               >= 4.6.0.1  && < 5,
-                       bytestring         >= 0.10.0.2 && < 0.11,
-                       containers         >= 0.5.0.0  && < 0.7,
-                       cryptonite         >= 0.10     && < 1.0,
-                       memory             >= 0.10     && < 1.0,
+                       base               >= 4.9      && < 5,
+                       base16-bytestring  >= 0.1      && < 1.1,
+                       bytestring         >= 0.10.0.2 && < 0.13,
+                       cryptohash-sha1    >= 0.11     && < 0.12,
                        directory          >= 1.2.0.1  && < 1.4,
                        expiring-cache-map >= 0.0.5    && < 0.1,
-                       filepath           >= 1.3.0.1  && < 1.5,
+                       filepath           >= 1.3.0.1  && < 1.6,
                        http-types         >= 0.8.2    && < 0.13,
                        mime-types         >= 0.1.0.3  && < 0.2,
-                       mtl                >= 2.1.2    && < 2.3,
-                       old-locale         >= 1.0      && < 1.1,
-                       semigroups         >= 0.18     && < 1,
-                       text               >= 0.11.3.1 && < 1.3,
-                       time               >= 1.4      && < 1.10,
+                       text               >= 0.11.3.1 && < 2.2,
+                       time               >= 1.6.0.1  && < 1.16,
                        wai                >= 3.0.0    && < 3.3
 
-  GHC-options: -Wall -fno-warn-orphans
+  GHC-options: -Wall -Wno-orphans
 
+test-suite spec
+  main-is:             Spec.hs
+  other-modules:       Network.Wai.Middleware.StaticSpec
+  type:                exitcode-stdio-1.0
+  default-language:    Haskell2010
+  hs-source-dirs:      test
+  build-depends:       base,
+                       hspec == 2.*,
+                       hspec-expectations-lifted,
+                       hspec-wai >= 0.6.3,
+                       mockery,
+                       scotty,
+                       wai-extra,
+                       http-types,
+                       wai-middleware-static
+  build-tool-depends:  hspec-discover:hspec-discover == 2.*
+  GHC-options:         -Wall
+
 source-repository head
   type:     git
-  location: git://github.com/scotty-web/wai-middleware-static.git
+  location: https://github.com/scotty-web/wai-middleware-static.git
