
Knocker, a knock based access control service for your homelab

Knocker is a self-hosted service that provides an HTTP based "knock-knock" single-packet authorization (SPA) gateway for your Homelab with web, cli + gnome and android clients. it can be used as authentication for your reverse proxy like Caddy, or even on the firewall level using the FirewallD integration. It allows you to keep your services completely private, opening them up on-demand only for authorized IP addresses.
This is ideal for homelab environments where you want to expose services to the internet without a persistent VPN connection, while minimizing your public-facing attack surface.
Knocker-Web Static PWA web app that supports knocking(whitelisting) on reload
Knocker-CLI A cli written in go with support for background knocks optionally trigged by ip chanages.
Knocker-gnome a gnome extension built on top of the Knocker-cli.
Knocker-EXPO An experimental Android App written in React EXPO with support for background knocking requests
sequenceDiagram
participant User
participant Caddy as Reverse Proxy (Caddy)
participant Knocker
participant Service as Protected Service
User->>Caddy: HTTP request to protected service
Caddy->>Knocker: GET /verify (copies X-Forwarded-For)
Knocker-->>Knocker: check always_allowed_ips / excluded_paths / whitelist
alt IP whitelisted
Knocker-->>Caddy: 200 OK (empty body)
Caddy->>Service: forward request
Service-->>Caddy: 200 OK
Caddy-->>User: 200 OK
else IP not whitelisted
Knocker-->>Caddy: 401 Unauthorized (empty body)
Caddy-->>User: 401 Unauthorized
end
Note over User,Knocker: Performing a "knock" (to add whitelist entry)
User->>Knocker: POST /knock (X-Api-Key, optional ip_address, ttl)
Knocker->>Knocker: validate API key, determine client IP
Knocker->>Knocker: update whitelist.json with expiry
Knocker-->>User: 200 OK (whitelisted_entry, expires_at, expires_in_seconds)
This project is designed to be deployed as a Docker container using the provided docker-compose.yml file. It uses the pre-built docker images with support for AMD64, ARMv8 and ARMv7
Knocker provides different image tags for different use cases:
latest Latest stable release (recommended for production)v1.2.3 Specific version tags (pinned versions)main Development branch (rolling updates, may be unstable)Configuration:
knocker.example.yaml to knocker.yaml.knocker.yaml; the example intentionally contains no usable defaults.trusted_proxies list in knocker.yaml: it must contain only the reverse proxy's own address(es), not its subnet. Because knocker only trusts X-Forwarded-For from a listed peer, a whole-subnet entry (e.g. 172.16.238.0/24) turns every container on that network into an identity authority -- any one of them could claim any client IP, or whitelist arbitrary addresses with a regular key. Docker doesn't assign stable container IPs by default, so pin the proxy with ipv4_address / ipv6_address (see the commented-out caddy service in docker-compose.yml) and list those exact /32 and /128 addresses.whitelist.storage_path under the app working directory, /data, or /tmp.firewalld.enabled: true and adjusting the related settings. Note: This requires the container to run as root.Run the Service:
docker compose up -d
This will pull the pre-built knocker image and start both the knocker and caddy services.
Knocker works by acting as an auth gateway for your reverse Proxy. It offers a verify endpoint, to check if the requesting IP is whitelisted or not, if not it will reply with a 401 and the reverse proxy will refuse the connection.
Caddy has the forward_auth directive to check connections using an auth endpoint.
Define a Reusable Snippet: It's best practice to define a snippet in your Caddyfile for the auth check.
Protect Your Services: Import the snippet for any service you want to protect.
Example Caddyfile:
# Caddyfile
# Define a reusable snippet for the knock-knock check.
# It points to the knocker service using Docker's internal DNS.
(knocker_auth) {
forward_auth knocker:8000 {
uri /verify
}
}
# The public endpoint for performing the knock.
# Make sure this domain points to your Caddy server's IP.
knock.your-domain.com {
request_body {
max_size 4KB
}
reverse_proxy knocker:8000
}
# An example protected service.
jellyfin.your-domain.com {
import knocker_auth # Apply the forward_auth check
reverse_proxy jellyfin_service_name:8096
}
When a user is not whitelisted, Caddy's forward_auth directive will return a 401 Unauthorized response with an empty body.
Important Note: Caddy's handle_errors directive does not work with forward_auth responses. The error response comes directly from the authentication service (knocker), not from Caddy itself, so handle_errors cannot intercept or modify these responses.
Knocker provides advanced firewall integration through firewalld, creating dynamic, time-based firewall rules that automatically expire based on the TTL specified in knock requests. This feature operates at the network level, allowing you to use knocker for non-http services like ssh or game servers.
sequenceDiagram
participant Client as User
participant Firewall as Firewalld (knocker zone)
participant Knocker
participant Service as Protected Service (port 22)
Note over Client,Firewall: Initial state — monitored port is blocked by default
Client->>Firewall: TCP SYN to Service:22
Firewall-->>Client: DROP (no response)
Note over Client,Knocker: User performs a knock to whitelist their IP
Client->>Knocker: POST /knock (X-Api-Key, optional ip_address, ttl)
Knocker->>Knocker: validate API key & determine client IP
Knocker->>Firewall: add rich accept rule for client IP on port 22 with timeout
Firewall-->>Knocker: success