diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,27 @@
 # Revision history for blockio
 
+## 0.2.1.0 -- 2026-08-27
+
+### Breaking changes
+
+None
+
+### New features
+
+* Support FreeBSD with an implementation using serial I/O. See [PR
+  #873][pr-873].
+
+### Minor changes
+
+* Explicitly mark the main library with `buildable: False` on unsupported OS
+  distributions. See [PR #873][pr-873].
+
+### Bug fixes
+
+None
+
+[pr-873]: https://github.com/IntersectMBO/lsm-tree/pull/873
+
 ## 0.2.0.0 -- 2026-05-13
 
 ### Breaking changes
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -18,11 +18,12 @@
 On Linux systems the *real* implementation is backed by
 [blockio-uring](https://hackage.haskell.org/package/blockio-uring), a library
 for asynchronous I/O that achieves good performance when performing batches
-concurrently. On Windows and MacOS systems the *real* implementation currently
-simply performs each I/O operation sequentially, which should achieve about the
-same performance as using non-batched I/O, but the library could be extended
-with asynchronous I/O implementations for Windows and MacOS as well. The
-simulated implementation also performs each I/O operation sequentially.
+concurrently. On Windows, MacOS, and FreeBSD systems the *real* implementation
+currently simply performs each I/O operation sequentially, which should achieve
+about the same performance as using non-batched I/O, but the library could be
+extended with asynchronous I/O implementations for Windows, MacOS, and FreeBSD
+as well. The simulated implementation also performs each I/O operation
+sequentially.
 
 As mentioned before, the batched I/O functionality is separated into an
 *abstract interface* and *implementations* of that abstract interface. The
diff --git a/blockio.cabal b/blockio.cabal
--- a/blockio.cabal
+++ b/blockio.cabal
@@ -1,6 +1,6 @@
 cabal-version:   3.4
 name:            blockio
-version:         0.2.0.0
+version:         0.2.1.0
 synopsis:        Perform batches of disk I/O operations.
 description:
   Perform batches of disk I\/O operations. Performing batches of disk I\/O can
@@ -46,7 +46,7 @@
   type:     git
   location: https://github.com/IntersectMBO/lsm-tree
   subdir:   blockio
-  tag:      blockio-0.2.0.0
+  tag:      blockio-0.2.1.0
 
 common warnings
   ghc-options:
@@ -114,6 +114,16 @@
     hs-source-dirs: src-windows
     build-depends:  Win32 ^>=2.14
     other-modules:  System.FS.BlockIO.Internal
+
+  elif os(freebsd)
+    hs-source-dirs: src-freebsd
+    build-depends:  unix ^>=2.8.4
+    other-modules:
+      System.FS.BlockIO.Internal
+      System.FS.BlockIO.Internal.Fcntl
+
+  else
+    buildable: False
 
   if flag(serialblockio)
     cpp-options: -DSERIALBLOCKIO
diff --git a/src-freebsd/System/FS/BlockIO/Internal.hs b/src-freebsd/System/FS/BlockIO/Internal.hs
new file mode 100644
--- /dev/null
+++ b/src-freebsd/System/FS/BlockIO/Internal.hs
@@ -0,0 +1,63 @@
+module System.FS.BlockIO.Internal (
+    ioHasBlockIO
+  ) where
+
+import qualified System.FS.API as FS
+import           System.FS.API (FsPath, Handle (..), HasFS)
+import           System.FS.BlockIO.API (Advice (..), FileOffset, HasBlockIO)
+import qualified System.FS.BlockIO.Internal.Fcntl as Fcntl
+import qualified System.FS.BlockIO.IO.Internal as IOI
+import           System.FS.IO (HandleIO)
+import qualified System.FS.IO.Handle as FS
+import qualified System.Posix.Fcntl as Fcntl (Advice (..), fileAdvise,
+                     fileAllocate)
+import qualified System.Posix.Files as Unix
+import qualified System.Posix.Unistd as Unix
+
+import qualified System.FS.BlockIO.Serial as Serial
+
+-- | For now we use the portable serial implementation of HasBlockIO. If you
+-- want to provide a proper async I\/O implementation for FreeBSD, then this is where
+-- you should put it.
+ioHasBlockIO ::
+     HasFS IO HandleIO
+  -> IOI.IOCtxParams
+  -> IO (HasBlockIO IO HandleIO)
+ioHasBlockIO hfs _params =
+    Serial.serialHasBlockIO
+      hSetNoCache
+      hAdvise
+      hAllocate
+      (IOI.tryLockFileIO hfs)
+      hSynchronise
+      (synchroniseDirectory hfs)
+      (IOI.createHardLinkIO hfs Unix.createLink)
+      hfs
+
+hSetNoCache :: Handle HandleIO -> Bool -> IO ()
+hSetNoCache h b =
+  FS.withOpenHandle "hSetNoCache" (handleRaw h) (flip Fcntl.fileSetCaching (not b))
+
+hAdvise :: Handle HandleIO -> FileOffset -> FileOffset -> Advice -> IO ()
+hAdvise h off len advice = FS.withOpenHandle "hAdvise" (handleRaw h) $ \fd ->
+    Fcntl.fileAdvise fd off len advice'
+  where
+    advice' = case advice of
+      AdviceNormal     -> Fcntl.AdviceNormal
+      AdviceRandom     -> Fcntl.AdviceRandom
+      AdviceSequential -> Fcntl.AdviceSequential
+      AdviceWillNeed   -> Fcntl.AdviceWillNeed
+      AdviceDontNeed   -> Fcntl.AdviceDontNeed
+      AdviceNoReuse    -> Fcntl.AdviceNoReuse
+
+hAllocate :: Handle HandleIO -> FileOffset -> FileOffset -> IO ()
+hAllocate h off len = FS.withOpenHandle "hAllocate" (handleRaw h) $ \fd ->
+    Fcntl.fileAllocate fd off len
+
+hSynchronise :: Handle HandleIO -> IO ()
+hSynchronise h = FS.withOpenHandle "hSynchronise" (handleRaw h) $ \fd ->
+    Unix.fileSynchronise fd
+
+synchroniseDirectory :: HasFS IO HandleIO -> FsPath -> IO ()
+synchroniseDirectory hfs path =
+    FS.withFile hfs path FS.ReadMode $ hSynchronise
diff --git a/src-freebsd/System/FS/BlockIO/Internal/Fcntl.hsc b/src-freebsd/System/FS/BlockIO/Internal/Fcntl.hsc
new file mode 100644
--- /dev/null
+++ b/src-freebsd/System/FS/BlockIO/Internal/Fcntl.hsc
@@ -0,0 +1,36 @@
+{-# LANGUAGE CPP #-}
+
+-- | Compatibility layer for the @unix@ package to provide a @fileSetCaching@ function.
+--
+-- @unix >= 2.8.7@ defines a @fileSetCaching@ function, but @unix < 2.8.7@ does not.
+-- This module defines the function for @unix@ versions @< 2.8.7@.
+-- The implementation is adapted from https://github.com/haskell/unix/blob/v2.8.8.0/System/Posix/Fcntl.hsc#L116-L182.
+--
+-- NOTE: in the future if we no longer support @unix@ versions @< 2.8.7@, then this module can be removed.
+module System.FS.BlockIO.Internal.Fcntl (fileSetCaching) where
+
+#if MIN_VERSION_unix(2,8,7)
+
+import System.Posix.Fcntl (fileSetCaching)
+
+#else
+
+#include "HsUnix.h"
+#include <fcntl.h>
+
+import           Data.Bits
+import           Foreign.C
+import           System.Posix.Internals
+import           System.Posix.Types (Fd (Fd))
+
+-- | For simplification, we considered that FreeBSD HAS_O_DIRECT
+fileSetCaching :: Fd -> Bool -> IO ()
+fileSetCaching (Fd fd) val = do
+    r <- throwErrnoIfMinus1 "fileSetCaching" (c_fcntl_read fd #{const F_GETFL})
+    let r' | val       = fromIntegral r .&. complement opt_val
+           | otherwise = fromIntegral r .|. opt_val
+    throwErrnoIfMinus1_ "fileSetCaching" (c_fcntl_write fd #{const F_SETFL} r')
+  where
+    opt_val = #{const O_DIRECT}
+
+#endif
diff --git a/src/System/FS/BlockIO/IO.hs b/src/System/FS/BlockIO/IO.hs
--- a/src/System/FS/BlockIO/IO.hs
+++ b/src/System/FS/BlockIO/IO.hs
@@ -3,7 +3,7 @@
 -- The implementation of the 'HasBlockIO' interface provided in this module is
 -- platform-dependent. Most importantly, on Linux, the implementation of
 -- 'submitIO' is backed by @blockio-uring@: a library for asynchronous I\/O. On
--- Windows and MacOS, the implementation of 'submitIO' only supports serial
+-- Windows, MacOS, and FreeBSD, the implementation of 'submitIO' only supports serial
 -- I\/O.
 module System.FS.BlockIO.IO (
     -- * Implementation details #impl#
@@ -39,7 +39,7 @@
 
   Note: if the @serialblockio@ Cabal flag is enabled, then the Linux
   implementation uses a mocked context and serial I\/O for 'close' and
-  'submitIO', just like the MacOS and Windows implementations do.
+  'submitIO', just like the MacOS, Windows, and FreeBSD implementations do.
 
   [IO context]:  When an instance of the 'HasBlockIO' interface for Linux
     systems is initialised, an @io_uring@ context is created using the
@@ -50,61 +50,71 @@
 
     * Linux: an @io_uring@ context provided by the @blockio-uring@ package
     * MacOS: a mocked context using an @MVar@
-    * Windows: a mocked conext using an @MVar@
+    * Windows: a mocked context using an @MVar@
+    * FreeBSD: a mocked context using an @MVar@
 
   ['close']:
 
     * Linux: close the @io_uring@ context through the @blockio-uring@ package
     * MacOS: close the mocked context
     * Windows: close the mocked context
+    * FreeBSD: close the mocked context
 
   ['submitIO']: Submit a batch of I\/O operations using:
 
     * Linux: the @submitIO@ function from the @blockio-uring@ package
     * MacOS: serial I\/O using a 'HasFS'
     * Windows: serial I\/O using a 'HasFS'
+    * FreeBSD: serial I\/O using a 'HasFS'
 
   ['hSetNoCache']:
 
     * Linux: set the @O_DIRECT@ flag
     * MacOS: set the @F_NOCACHE@ flag
     * Windows: no-op
+    * FreeBSD: set the @O_DIRECT@ flag
 
   ['hAdvise']:
 
     * Linux: perform @posix_fadvise(2)@
     * MacOS: no-op
     * Windows: no-op
+    * FreeBSD: perform @posix_fadvise(2)@
 
   ['hAllocate']:
 
     * Linux: perform @posix_fallocate(2)@
     * MacOS: no-op
     * Windows: no-op
+    * FreeBSD: perform @posix_fallocate(2)@
 
   ['tryLockFile']: This uses different locking methods depending on the OS.
 
     * Linux: Open file descriptor (OFD)
     * MacOS: @flock@
     * Windows: @LockFileEx@
+    * FreeBSD: Open file descriptor (OFD)
 
   ['hSynchronise']:
 
     * Linux: perform @fsync(2)@
     * MacOS: perform @fsync(2)@
     * Windows: perform @flushFileBuffers@
+    * FreeBSD: perform @fsync(2)@
 
   ['synchroniseDirectory']:
 
     * Linux: perform @fsync(2)@
     * MacOS: perform @fsync(2)@
     * Windows: no-op
+    * FreeBSD: perform @fsync(2)@
 
   ['createHardLink']:
 
     * Linux: perform @link@
     * MacOS: perform @link@
     * Windows: perform @CreateHardLinkW@
+    * FreeBSD: perform @link@
 -}
 
 -- | An implementation of the 'HasBlockIO' interface using the real file system.
diff --git a/src/System/FS/BlockIO/IO/Internal.hs b/src/System/FS/BlockIO/IO/Internal.hs
--- a/src/System/FS/BlockIO/IO/Internal.hs
+++ b/src/System/FS/BlockIO/IO/Internal.hs
@@ -37,6 +37,7 @@
 --  * Linux: Pass the parameters to 'initIOCtx' in the @blockio-uring@ package
 --  * MacOS: Ignore the parameters
 --  * Windows: Ignore the parameters
+--  * FreeBSD: Ignore the parameters
 --
 --  For more information about what these parameters mean and how to configure
 --  them, see the @blockio-uring@ package.
