diff --git a/flake.lock b/flake.lock
index d761f4f..d86b361 100644
--- a/flake.lock
+++ b/flake.lock
@@ -190,6 +190,22 @@
         "type": "github"
       }
     },
+    "nixpkgs-crowdsec": {
+      "locked": {
+        "lastModified": 1738085579,
+        "narHash": "sha256-7mLjMrOiiIi0vI7BJwbEipYQzwA7JF/NWHP+LM4q5S8=",
+        "owner": "tlater",
+        "repo": "nixpkgs",
+        "rev": "426a7afc9a6ecfdac544bda4022acef31e36df34",
+        "type": "github"
+      },
+      "original": {
+        "owner": "tlater",
+        "ref": "tlater/fix-crowdsec",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
     "nixpkgs-unstable": {
       "locked": {
         "lastModified": 1737192615,
@@ -310,6 +326,7 @@
         "disko": "disko",
         "foundryvtt": "foundryvtt",
         "nixpkgs": "nixpkgs_2",
+        "nixpkgs-crowdsec": "nixpkgs-crowdsec",
         "nixpkgs-unstable": "nixpkgs-unstable",
         "sonnenshift": "sonnenshift",
         "sops-nix": "sops-nix",
diff --git a/flake.nix b/flake.nix
index 2253566..444d4b8 100644
--- a/flake.nix
+++ b/flake.nix
@@ -26,6 +26,8 @@
       url = "git+ssh://git@github.com/sonnenshift/battery-manager";
       inputs.nixpkgs.follows = "nixpkgs";
     };
+
+    nixpkgs-crowdsec.url = "github:tlater/nixpkgs/tlater/fix-crowdsec";
   };
 
   outputs =
@@ -98,7 +100,10 @@
       # Garbage collection root #
       ###########################
 
-      packages.${system}.default = vm.config.system.build.vm;
+      packages.${system} = {
+        default = vm.config.system.build.vm;
+        crowdsec = pkgs.callPackage "${inputs.nixpkgs-crowdsec}/pkgs/by-name/cr/crowdsec/package.nix" { };
+      };
 
       ###################
       # Utility scripts #
diff --git a/modules/crowdsec.nix b/modules/crowdsec.nix
new file mode 100644
index 0000000..94798ac
--- /dev/null
+++ b/modules/crowdsec.nix
@@ -0,0 +1,270 @@
+{
+  flake-inputs,
+  pkgs,
+  lib,
+  config,
+  ...
+}:
+let
+  cfg = config.services.crowdsec;
+  settingsFormat = pkgs.formats.yaml { };
+
+  crowdsec = flake-inputs.self.packages.${pkgs.system}.crowdsec;
+  cscli = pkgs.writeShellScriptBin "cscli" ''
+    export PATH="$PATH:${crowdsec}/bin/"
+
+    cd ${cfg.stateDirectory}
+    sudo=exec
+    if [ "$USER" != "crowdsec" ]; then
+        sudo='exec /run/wrappers/bin/sudo -u crowdsec'
+    fi
+
+    $sudo ${crowdsec}/bin/cscli "$@"
+  '';
+in
+{
+  options.services.crowdsec =
+    let
+      inherit (lib.types) nullOr package path;
+    in
+    {
+      enable = lib.mkEnableOption "crowdsec";
+
+      package = lib.mkOption {
+        type = package;
+        default = crowdsec;
+      };
+
+      stateDirectory = lib.mkOption {
+        type = path;
+        readOnly = true;
+
+        description = ''
+          The state directory of the crowdsec instance. Cannot be
+          changed, but is exposed for downstream use.
+        '';
+      };
+
+      settings = lib.mkOption {
+        inherit (settingsFormat) type;
+        default = { };
+
+        description = ''
+          The crowdsec configuration. Refer to
+          <https://docs.crowdsec.net/docs/next/configuration/crowdsec_configuration/>
+          for details on supported values.
+        '';
+      };
+
+      centralApiCredentials = lib.mkOption {
+        type = nullOr path;
+        default = null;
+
+        description = ''
+          The API key to access crowdsec's central API - this is
+          required to access any of the shared blocklists.
+
+          Use of this feature is optional, entering no API key (the
+          default) turns all sharing or receiving of blocked IPs off.
+
+          Note that adding the API key by itself does not enable
+          sharing of blocked IPs with the central API. This limits the
+          types of blocklists this instance can access.
+
+          To also turn sharing blocked IPs on, set
+          `api.server.online_client.sharing = true;`.
+        '';
+      };
+
+      ctiApiKey = lib.mkOption {
+        type = nullOr path;
+        default = null;
+
+        description = ''
+          The API key for crowdsec's CTI offering.
+        '';
+      };
+    };
+
+  config = lib.mkIf cfg.enable {
+    # Set up default settings; anything that *shouldn't* be changed is
+    # set to the default priority so that users need to use
+    # `lib.mkForce`.
+    services.crowdsec = {
+      stateDirectory = "/var/lib/crowdsec";
+
+      settings = {
+        common = {
+          daemonize = true;
+          # The default logs to files, which isn't the preferred way
+          # on NixOS
+          log_media = "stdout";
+        };
+
+        config_paths = {
+          config_dir = "/etc/crowdsec/";
+          data_dir = "${cfg.stateDirectory}/data/";
+          # This "config" file is intended to be written to using the
+          # cscli tool, so you can temporarily make it so rules don't
+          # do anything but log what they *would* do for
+          # experimentation.
+          simulation_path = "${cfg.stateDirectory}/config/simulation.yaml";
+
+          pattern_dir = lib.mkDefault "${cfg.package}/share/crowdsec/config/patterns";
+
+          # We don't want to actually download anything; Any rules
+          # will be properly packaged.
+          hub_dir = lib.mkDefault "/var/empty/";
+          index_path = lib.mkDefault (pkgs.writeText "crowdsec-index.json" "{}");
+
+          # Integrations aren't supported for now
+          notification_dir = lib.mkDefault "/var/empty/";
+          plugin_dir = lib.mkDefault "/var/empty/";
+        };
+
+        crowdsec_service.acquisition_path = lib.mkDefault "${cfg.package}/share/crowdsec/config/acquis.yaml";
+
+        cscli = {
+          prometheus_uri = lib.mkDefault "127.0.0.1:6060";
+        };
+
+        db_config =
+          let
+            default_db = cfg.settings.db_config.type == "sqlite";
+          in
+          {
+            type = lib.mkDefault "sqlite";
+            db_path = lib.mkIf default_db (lib.mkDefault "${cfg.stateDirectory}/data/crowdsec.db");
+            use_wal = lib.mkIf default_db (lib.mkDefault true);
+            flush = {
+              max_items = lib.mkDefault 5000;
+              max_age = lib.mDefault "7d";
+            };
+          };
+
+        api = {
+          cti = {
+            enabled = cfg.ctiApiKey != null;
+            key = cfg.ctiApiKey;
+          };
+          client.credentials_path = "${cfg.stateDirectory}/local_credentials.yaml";
+          server = {
+            listen_uri = lib.mkDefault "127.0.0.1:8080";
+            profiles_path = lib.mkDefault "${cfg.package}/share/crowdsec/config/profiles.yaml";
+            console_path = lib.mkDefault "${cfg.package}/share/crowdsec/config/console.yaml";
+
+            online_client = {
+              # By default, we don't let crowdsec phone home, since
+              # this is usually within NixOS users' concerns.
+              #
+              # TODO: Enable when this option becomes available
+              # (1.6.4, current nixpkgs-unstable)
+              # sharing = lib.mkDefault false;
+              credentials_path = cfg.centralApiCredentials;
+            };
+          };
+        };
+
+        # We enable prometheus by default, since cscli relies on it
+        # for metrics
+        prometheus = {
+          enabled = lib.mkDefault true;
+          level = lib.mkDefault "full";
+          listen_addr = lib.mkDefault "127.0.0.1";
+          listen_port = lib.mkDefault 6060;
+        };
+      };
+    };
+
+    systemd.packages = [
+      cfg.package
+    ];
+
+    environment = {
+      systemPackages = [ cscli ];
+      etc."crowdsec/config.yaml".source = settingsFormat.generate "crowdsec-settings.yaml" cfg.settings;
+    };
+
+    systemd.services = {
+      crowdsec-setup = {
+        description = "Crowdsec database and config preparation";
+
+        script = ''
+          mkdir -p '${cfg.stateDirectory}/'{config,}
+
+          if [ ! -e '${cfg.settings.config_paths.simulation_path}' ]; then
+              cp '${cfg.package}/share/crowdsec/config/simulation.yaml' '${cfg.settings.config_paths.simulation_path}'
+          fi
+
+          if [ ! -e '${cfg.settings.api.client.credentials_path}' ]; then
+              ${cfg.package}/bin/cscli machines add --auto --file '${cfg.settings.api.client.credentials_path}'
+          fi
+        '';
+
+        serviceConfig = {
+          User = "crowdsec";
+          Group = "crowdsec";
+          StateDirectory = "crowdsec";
+
+          Type = "oneshot";
+          RemainAfterExit = true;
+        };
+      };
+
+      # Note that the service basics are already defined upstream
+      crowdsec = {
+        after = [ "crowdsec-setup.service" ];
+        bindsTo = [ "crowdsec-setup.service" ];
+
+        serviceConfig = {
+          User = "crowdsec";
+          Group = "crowdsec";
+          SupplementaryGroups = [ "systemd-journal" ];
+
+          StateDirectory = "crowdsec";
+
+          # PrivateTmp = true;
+          # PrivateUsers = true;
+          # ProtectHome = true;
+          # CapabilityBoundingSet = [ ];
+          # LockPersonality = true;
+          # PrivateDevices = true;
+          # ProtectHostname = true;
+          # ProtectKernelTunables = true;
+          # ProtectKernelModules = true;
+          # ProtectControlGroups = true;
+
+          # NoNewPrivileges = true;
+          # RestrictSUIDSGID = true;
+
+          # ProtectProc = "invisible";
+          # ProcSubset = "pid"; # Needed for journal access
+
+          # RestrictNamespaces = true;
+          # RestrictRealtime = true;
+
+          # SystemCallFilter = [
+          #   "@system-service"
+          #   "@network-io"
+          # ];
+          # SystemCallArchitectures = [ "native" ];
+          # SystemCallErrorNumber = "EPERM";
+
+          # ExecPaths = [ "/nix/store" ];
+          # NoExecPaths = [ "/" ];
+        };
+      };
+    };
+
+    users = {
+      users.crowdsec = {
+        isSystemUser = true;
+        home = cfg.stateDirectory;
+        group = "crowdsec";
+      };
+      groups = {
+        crowdsec = { };
+      };
+    };
+  };
+}
diff --git a/modules/default.nix b/modules/default.nix
index e1db4cc..977539a 100644
--- a/modules/default.nix
+++ b/modules/default.nix
@@ -1 +1,6 @@
-{ imports = [ ./nginxExtensions.nix ]; }
+{
+  imports = [
+    ./crowdsec.nix
+    ./nginxExtensions.nix
+  ];
+}