6.9 KiB
Installation
This document covers both generic Linux deployments and NixOS deployments.
Prerequisites
You need:
- Linux.
- Caddy access logs written to files in the default JSON format.
- One readable log file per logical source you want to monitor.
- A writable directory for the SQLite database.
- Go 1.25 or newer if you build from source without Nix.
- Optional: OPNsense API credentials if you want firewall alias synchronization.
- Optional: outbound DNS and HTTPS access if you want IP investigation and bot verification.
Deployment checklist
Before starting the daemon, decide:
- which Caddy log files you want to ingest
- which heuristic profile each log file should use
- whether the daemon should auto-block for each profile or only mark addresses for review
- whether OPNsense integration is enabled
- which address the web UI should listen on
1. Configure Caddy access logs
The daemon reads files, not stdout and not journald. Each monitored source should write to its own file.
An example Caddy configuration is available in ../examples/Caddyfile.
Important points:
- Keep the default JSON log format.
- Use one file per source.
- Make sure the service account running
caddy-opnsense-blockercan read those files. - If Caddy sits behind another proxy, make sure Caddy records the actual client IP in
request.client_ip.
2. Prepare OPNsense credentials (optional)
If you want the daemon to block and unblock IP addresses on OPNsense:
- Create or reuse an API key and secret.
- Decide which alias name should contain blocked IPs.
- Keep the credentials in files readable only by the service account.
If OPNsense integration is disabled, the daemon still ingests logs, records decisions, and supports manual review.
3. Build the binary
Clone the repository and build the daemon:
git clone https://git.dern.ovh/infrastructure/caddy-opnsense-blocker.git
cd caddy-opnsense-blocker
CGO_ENABLED=0 go test ./...
CGO_ENABLED=0 go build -o caddy-opnsense-blocker ./cmd/caddy-opnsense-blocker
You can also use nix-build if you prefer the Nix package defined in this repository.
4. Create the runtime user and directories
Example for a non-NixOS host:
useradd --system --home-dir /var/lib/caddy-opnsense-blocker --create-home --shell /usr/sbin/nologin blocker
install -d -o blocker -g blocker -m 0750 /var/lib/caddy-opnsense-blocker
install -d -o root -g blocker -m 0750 /etc/caddy-opnsense-blocker
install -d -o root -g blocker -m 0750 /var/log/caddy
install -m 0755 caddy-opnsense-blocker /usr/local/bin/caddy-opnsense-blocker
If your Caddy logs are group-readable by a different group, either adjust permissions or grant the runtime user supplementary group access.
5. Create the configuration file
Start from the repository example:
cp config.example.yaml /etc/caddy-opnsense-blocker/config.yaml
chmod 0640 /etc/caddy-opnsense-blocker/config.yaml
Then edit:
server.listen_addressstorage.pathprofilessourcesopnsense.*if OPNsense integration is enabled
For a field-by-field reference, see configuration.md.
Store OPNsense credentials in files such as:
install -m 0400 -o root -g blocker /path/to/api-key /etc/caddy-opnsense-blocker/opnsense-api-key
install -m 0400 -o root -g blocker /path/to/api-secret /etc/caddy-opnsense-blocker/opnsense-api-secret
Then reference those files from config.yaml.
6. Run the daemon manually once
Before installing a service unit, validate that the daemon starts correctly:
sudo -u blocker /usr/local/bin/caddy-opnsense-blocker -config /etc/caddy-opnsense-blocker/config.yaml
In another terminal:
curl http://127.0.0.1:9080/healthz
If the daemon is bound to another address, adjust the URL accordingly.
7. Install the systemd service
An example unit file is available in ../examples/caddy-opnsense-blocker.service.
Typical installation steps:
cp examples/caddy-opnsense-blocker.service /etc/systemd/system/caddy-opnsense-blocker.service
systemctl daemon-reload
systemctl enable --now caddy-opnsense-blocker.service
After that:
systemctl status caddy-opnsense-blocker.service
journalctl -u caddy-opnsense-blocker.service -f
8. Verify end-to-end behavior
Recommended checks:
GET /healthzreturns{"status":"ok",...}- the UI loads on the configured address
- new suspicious requests appear in the dashboard
- the IP detail page shows the full request history for one address
- manual
BlockandUnblockactions produce backend actions if OPNsense is enabled
9. Log rotation and upgrades
- The daemon stores inode and offset per source, so it can resume after restarts.
- File truncation or inode changes are detected automatically, which makes the common rename-based log rotation pattern safe.
- Upgrades are usually just “replace the binary, restart the service”. The SQLite database is kept separately in the configured state directory.
Nix build
Build the packaged binary directly from the repository root:
nix-build
The result symlink contains the packaged daemon under result/bin/caddy-opnsense-blocker.
NixOS module
The repository includes a reusable NixOS module in ../module.nix.
Import from a local checkout
{
imports = [ /path/to/caddy-opnsense-blocker/module.nix ];
services.caddy-opnsense-blocker = {
enable = true;
credentials.opnsenseApiKeyFile = "/run/secrets/opnsense-api-key";
credentials.opnsenseApiSecretFile = "/run/secrets/opnsense-api-secret";
settings = {
server.listen_address = "127.0.0.1:9080";
opnsense = {
enabled = true;
base_url = "https://router.example.test";
ensure_alias = true;
alias.name = "blocked-ips";
};
profiles.public-web = {
auto_block = true;
block_unexpected_posts = true;
block_php_paths = true;
suspicious_path_prefixes = [ "/wp-admin" "/wp-login.php" "/.env" ];
};
sources = [
{
name = "public-web";
path = "/var/log/caddy/public-web-access.json";
profile = "public-web";
}
];
};
};
}
Notes about the NixOS module
- The module defaults the service user and group to
caddy. - The generated configuration file is written from
services.caddy-opnsense-blocker.settings. - OPNsense credentials can be injected through systemd credentials with
credentials.opnsenseApiKeyFileandcredentials.opnsenseApiSecretFile. - The default database path is
/var/lib/caddy-opnsense-blocker/caddy-opnsense-blocker.db.
Operating without OPNsense
Set opnsense.enabled: false to run in review-only mode.
In that mode the daemon still:
- ingests logs
- stores events and IP state
- runs investigations
- serves the UI and API
- records manual override decisions locally
It simply stops short of calling an external firewall API.