API
Role of the API
The BunkerWeb API is the control plane for managing instances, services, bans, plugins, jobs, and custom configs. It runs as a FastAPI app behind Gunicorn and should stay on a trusted network. Interactive docs live at /docs (or <API_ROOT_PATH>/docs); the OpenAPI schema is at /openapi.json.
Keep it private
Do not expose the API directly to the Internet. Keep it on an internal network, restrict source IPs, and require authentication.
Quick facts
- Health endpoints:
GET /pingandGET /health - Root path: set
API_ROOT_PATHwhen reverse-proxying on a sub-path so docs and OpenAPI links work - Auth is mandatory: Biscuit tokens, admin Basic, or an override Bearer token
- IP allowlist defaults to RFC1918 ranges (
API_WHITELIST_IPS); disable only if upstream controls access - Rate limiting defaults on;
/authalways has its own limit
Security checklist
- Network: keep traffic internal; bind to loopback or an internal interface and restrict source IPs with
API_WHITELIST_IPS(enabled by default). - Auth present: set
API_USERNAME/API_PASSWORD(admin) and, if needed,API_ACL_BOOTSTRAP_FILEfor extra users/ACLs; keep an overrideAPI_TOKENonly for break-glass use. - Path hiding: when reverse-proxying, pick an unguessable
API_ROOT_PATHand mirror it on the proxy. - Rate limiting: leave it on unless another layer enforces equivalent limits;
/authis always rate limited. - TLS: terminate TLS at the proxy or set
API_SSL_ENABLED=yeswith cert/key paths.
Run it
Choose the flavor that matches your environment.
Minimal Compose-style layout with the API behind BunkerWeb. Adjust versions and passwords before use.
x-bw-env: &bw-env
# We use an anchor to avoid repeating the same settings for both services
API_WHITELIST_IP: "127.0.0.0/8 10.20.30.0/24" # Make sure to set the correct IP range so the scheduler can send the configuration to the instance (internal BunkerWeb API)
# Optional: set an API token and mirror it in both containers (internal BunkerWeb API)
API_TOKEN: ""
DATABASE_URI: "mariadb+pymysql://bunkerweb:changeme@bw-db:3306/db" # Remember to set a stronger password for the database
services:
bunkerweb:
# This is the name that will be used to identify the instance in the Scheduler
image: bunkerity/bunkerweb:testing
ports:
- "80:8080/tcp"
- "443:8443/tcp"
- "443:8443/udp" # For QUIC / HTTP3 support
environment:
<<: *bw-env # We use the anchor to avoid repeating the same settings for all services
restart: "unless-stopped"
networks:
- bw-universe
- bw-services
bw-scheduler:
image: bunkerity/bunkerweb-scheduler:testing
environment:
<<: *bw-env
BUNKERWEB_INSTANCES: "bunkerweb" # Make sure to set the correct instance name
SERVER_NAME: "api.example.com"
MULTISITE: "yes"
USE_REDIS: "yes"
REDIS_HOST: "redis"
DISABLE_DEFAULT_SERVER: "yes"
AUTO_LETS_ENCRYPT: "yes"
api.example.com_USE_TEMPLATE: "api"
api.example.com_USE_REVERSE_PROXY: "yes"
api.example.com_REVERSE_PROXY_URL: "/"
api.example.com_REVERSE_PROXY_HOST: "http://bw-api:8888"
volumes:
- bw-storage:/data # This is used to persist the cache and other data like the backups
restart: "unless-stopped"
networks:
- bw-universe
- bw-db
bw-api:
image: bunkerity/bunkerweb-api:testing
environment:
<<: *bw-env
API_USERNAME: "admin"
API_PASSWORD: "Str0ng&P@ss!"
# API_TOKEN: "admin-override-token" # optional
FORWARDED_ALLOW_IPS: "*" # Be careful with this setting; only use it if you are sure that the reverse proxy is the only way to access the API
API_ROOT_PATH: "/"
networks:
- bw-universe
- bw-db
bw-db:
image: mariadb:11
# We set the max allowed packet size to avoid issues with large queries
command: --max-allowed-packet=67108864
environment:
MYSQL_RANDOM_ROOT_PASSWORD: "yes"
MYSQL_DATABASE: "db"
MYSQL_USER: "bunkerweb"
MYSQL_PASSWORD: "changeme" # Remember to set a stronger password for the database
volumes:
- bw-data:/var/lib/mysql
restart: "unless-stopped"
networks:
- bw-db
redis: # Redis service for the persistence of reports/bans/stats
image: redis:8-alpine
command: >
redis-server
--maxmemory 256mb
--maxmemory-policy allkeys-lru
--save 60 1000
--appendonly yes
volumes:
- redis-data:/data
restart: "unless-stopped"
networks:
- bw-universe
volumes:
bw-data:
bw-storage:
redis-data:
networks:
bw-universe:
name: bw-universe
ipam:
driver: default
config:
- subnet: 10.20.30.0/24 # Make sure to set the correct IP range so the scheduler can send the configuration to the instance
bw-services:
name: bw-services
bw-db:
name: bw-db
docker run -d \
--name bunkerweb-aio \
-e SERVICE_API=yes \
-e API_WHITELIST_IPS="127.0.0.0/8" \
-p 80:8080/tcp -p 443:8443/tcp -p 443:8443/udp \
bunkerity/bunkerweb-all-in-one:testing
The DEB/RPM packages ship bunkerweb-api.service, managed through /usr/share/bunkerweb/scripts/bunkerweb-api.sh.
- Enable/start:
sudo systemctl enable --now bunkerweb-api.service - Reload:
sudo systemctl reload bunkerweb-api.service - Logs: journal plus
/var/log/bunkerweb/api.log - Default listen:
127.0.0.1:8888withAPI_WHITELIST_IPS=127.0.0.1 - Config files:
/etc/bunkerweb/api.env(auto-created with commented defaults on first start) and/etc/bunkerweb/api.yml - Environment sources:
api.env,variables.env,/run/secrets/<VAR>, then exported to the Gunicorn process
Edit /etc/bunkerweb/api.env to set API_USERNAME/API_PASSWORD, allowlist, TLS, rate limits, or API_ROOT_PATH, then systemctl reload bunkerweb-api.
Authentication and authorization
/authissues Biscuit tokens. Credentials can come from Basic auth, form fields, JSON body, or a Bearer header equal toAPI_TOKEN(admin override).- Admin users can also call protected routes directly with HTTP Basic (no Biscuit needed).
- If the Bearer token matches
API_TOKEN, access is full/admin. Otherwise the Biscuit guard enforces ACL. - Biscuit payload includes user, time, client IP, host, version, a coarse
role("api_user", ["read", "write"]), and eitheradmin(true)or fine-grainedapi_perm(resource_type, resource_id|*, permission). - TTL is
API_BISCUIT_TTL_SECONDS(0/off disables expiry). Keys live at/var/lib/bunkerweb/.api_biscuit_private_keyand.api_biscuit_public_keyunless provided viaBISCUIT_PRIVATE_KEY/BISCUIT_PUBLIC_KEY. - Auth endpoints are exposed only when at least one API user exists in the database.
Auth quickstart
- Set
API_USERNAMEandAPI_PASSWORD(andOVERRIDE_API_CREDS=yesif you need to re-seed). - Call
POST /authwith Basic auth; read.tokenfrom the response. - Use
Authorization: Bearer <token>on subsequent calls.
Permissions and ACL
- Coarse role: GET/HEAD/OPTIONS require
read; write verbs requirewrite. - Fine-grained ACL is enforced when routes declare required permissions;
admin(true)bypasses checks. - Resource types:
instances,global_settings,services,configs,plugins,cache,bans,jobs. - Permission names:
instances_*:instances_read,instances_update,instances_delete,instances_create,instances_executeglobal_settings_*:global_settings_read,global_settings_updateservices:service_read,service_create,service_update,service_delete,service_convert,service_exportconfigs:configs_read,config_read,config_create,config_update,config_deleteplugins:plugin_read,plugin_create,plugin_deletecache:cache_read,cache_deletebans:ban_read,ban_update,ban_delete,ban_createdjobs:job_read,job_runresource_idis usually the second path component (e.g./services/{id});"*"grants global access.- Bootstrap non-admin users and grants with
API_ACL_BOOTSTRAP_FILEor a mounted/var/lib/bunkerweb/api_acl_bootstrap.json. Passwords may be plaintext or bcrypt hashes.
Minimal ACL bootstrap
{
"users": {
"ci": {
"admin": false,
"password": "Str0ng&P@ss!",
"permissions": {
"services": { "*": { "service_read": true } },
"configs": { "*": { "config_read": true, "config_update": true } }
}
}
}
}
Rate limiting
Enabled by default with two strings: API_RATE_LIMIT (global, default 100r/m) and API_RATE_LIMIT_AUTH (default 10r/m or off). Rates accept NGINX-style notation (3r/s, 40r/m, 200r/h) or verbose forms (100/minute, 200 per 30 minutes). Configure via:
API_RATE_LIMIT,API_RATE_LIMIT_AUTHAPI_RATE_LIMIT_ENABLED,API_RATE_LIMIT_HEADERS_ENABLEDAPI_RATE_LIMIT_RULES(CSV/JSON/YAML string or file path)API_RATE_LIMIT_STRATEGY,API_RATE_LIMIT_KEY,API_RATE_LIMIT_EXEMPT_IPS- Storage is in-memory or Redis/Valkey when
USE_REDIS=yesplusREDIS_*settings (Sentinel supported).
Limiter strategies (powered by limits):
fixed-window(default): bucket resets at each interval boundary; cheapest and fine for coarse limits.moving-window: true rolling window using precise timestamps; smoother but heavier on storage operations.sliding-window-counter: hybrid that smooths with weighted counts from the previous window; lighter than moving but smoother than fixed.
More detail and trade-offs: https://limits.readthedocs.io/en/stable/strategies.html
Inline CSV
API_RATE_LIMIT_RULES='POST /auth 10r/m, GET /instances* 200r/m, POST|PATCH /services* 40r/m'
YAML file
API_RATE_LIMIT: 200r/m
API_RATE_LIMIT_AUTH: 15r/m
API_RATE_LIMIT_RULES:
- path: "/auth"
methods: "POST"
rate: "10r/m"
- path: "/instances*"
methods: "GET|POST"
rate: "100r/m"
Configuration sources and precedence
- Environment variables (including Docker/Compose
environment:) - Secrets in
/run/secrets/<VAR>(Docker) - YAML at
/etc/bunkerweb/api.yml - Env file at
/etc/bunkerweb/api.env - Built-in defaults
Disable docs or schema by setting their URLs to off|disabled|none|false|0. Set API_SSL_ENABLED=yes with API_SSL_CERTFILE and API_SSL_KEYFILE to terminate TLS in the API. When reverse-proxying, set API_FORWARDED_ALLOW_IPS to the proxy IPs so Gunicorn trusts X-Forwarded-* headers.
Configuration reference (power users)
Surface & docs
| Setting | Description | Accepted values | Default |
|---|---|---|---|
API_DOCS_URL, API_REDOC_URL, API_OPENAPI_URL |
Paths for Swagger, ReDoc, and OpenAPI schema; set to off/disabled/none/false/0 to disable |
Path or off |
/docs, /redoc, /openapi.json |
API_ROOT_PATH |
Mount prefix when reverse-proxying | Path (e.g. /api) |
empty |
API_FORWARDED_ALLOW_IPS |
Trusted proxy IPs for X-Forwarded-* |
Comma-separated IPs/CIDRs | 127.0.0.1 (package default) |
Auth, ACL, Biscuit
| Setting | Description | Accepted values | Default |
|---|---|---|---|
API_USERNAME, API_PASSWORD |
Bootstrap admin user | Strings; strong password required in non-debug | unset |
OVERRIDE_API_CREDS |
Re-apply admin creds on startup | yes/no/on/off/true/false/0/1 |
no |
API_TOKEN |
Admin override Bearer token | Opaque string | unset |
API_ACL_BOOTSTRAP_FILE |
Path to JSON for users/permissions | File path or mounted /var/lib/bunkerweb/api_acl_bootstrap.json |
unset |
BISCUIT_PRIVATE_KEY, BISCUIT_PUBLIC_KEY |
Biscuit keys (hex) if not using files | Hex strings | auto-generated/persisted |
API_BISCUIT_TTL_SECONDS |
Token lifetime; 0/off disables expiry |
Integer seconds or off/disabled |
3600 |
CHECK_PRIVATE_IP |
Bind Biscuit to client IP (except private) | yes/no/on/off/true/false/0/1 |
yes |
Allowlist
| Setting | Description | Accepted values | Default |
|---|---|---|---|
API_WHITELIST_ENABLED |
Toggle IP allowlist middleware | yes/no/on/off/true/false/0/1 |
yes |
API_WHITELIST_IPS |
Space/comma-separated IPs/CIDRs | IPs/CIDRs | RFC1918 ranges in code |
Rate limiting
| Setting | Description | Accepted values | Default |
|---|---|---|---|
API_RATE_LIMIT |
Global limit (NGINX-style string) | 3r/s, 100/minute, 500 per 30 minutes |
100r/m |
API_RATE_LIMIT_AUTH |
/auth limit (or off) |
same as above or off/disabled/none/false/0 |
10r/m |
API_RATE_LIMIT_ENABLED |
Enable limiter | yes/no/on/off/true/false/0/1 |
yes |
API_RATE_LIMIT_HEADERS_ENABLED |
Inject rate limit headers | same as above | yes |
API_RATE_LIMIT_RULES |
Per-path rules (CSV/JSON/YAML or file path) | String or path | unset |
API_RATE_LIMIT_STRATEGY |
Algorithm | fixed-window, moving-window, sliding-window-counter |
fixed-window |
API_RATE_LIMIT_KEY |
Key selector | ip, header:<Name> |
ip |
API_RATE_LIMIT_EXEMPT_IPS |
Skip limits for these IPs/CIDRs | Space/comma-separated | unset |
API_RATE_LIMIT_STORAGE_OPTIONS |
JSON merged into storage config | JSON string | unset |
Redis/Valkey (for rate limits)
| Setting | Description | Accepted values | Default |
|---|---|---|---|
USE_REDIS |
Enable Redis backend | yes/no/on/off/true/false/0/1 |
no |
REDIS_HOST, REDIS_PORT, REDIS_DATABASE |
Connection details | Host, int, int | unset, 6379, 0 |
REDIS_USERNAME, REDIS_PASSWORD |
Auth | Strings | unset |
REDIS_SSL, REDIS_SSL_VERIFY |
TLS and verification | yes/no/on/off/true/false/0/1 |
no, yes |
REDIS_TIMEOUT |
Timeout (ms) | Integer | 1000 |
REDIS_KEEPALIVE_POOL |
Pool keepalive | Integer | 10 |
REDIS_SENTINEL_HOSTS |
Sentinel hosts | Space-separated host:port |
unset |
REDIS_SENTINEL_MASTER |
Sentinel master name | String | unset |
REDIS_SENTINEL_USERNAME, REDIS_SENTINEL_PASSWORD |
Sentinel auth | Strings | unset |
DB-provided Redis
If Redis/Valkey settings are present in the BunkerWeb database configuration, the API will automatically reuse them for rate limiting even without USE_REDIS set in the environment. Override via environment variables when you need a different backend.
Listener & TLS
| Setting | Description | Accepted values | Default |
|---|---|---|---|
API_LISTEN_ADDR, API_LISTEN_PORT |
Bind address/port for Gunicorn | IP or hostname, int | 127.0.0.1, 8888 (package script) |
API_SSL_ENABLED |
Enable TLS in API | yes/no/on/off/true/false/0/1 |
no |
API_SSL_CERTFILE, API_SSL_KEYFILE |
PEM cert and key paths | File paths | unset |
API_SSL_CA_CERTS |
Optional CA/chain | File path | unset |
Logging & runtime (package defaults)
| Setting | Description | Accepted values | Default |
|---|---|---|---|
LOG_LEVEL, CUSTOM_LOG_LEVEL |
Base log level / override | debug/info/warning/error/critical |
info |
LOG_TYPES |
Destinations | file, stdout, syslog (comma-separated) |
file |
LOG_FILE_PATH |
Log file location | Path | /var/log/bunkerweb/api.log |
LOG_SYSLOG_TAG |
Syslog tag | String | bw-api |
MAX_WORKERS, MAX_THREADS |
Gunicorn workers/threads | Integer or unset for auto | unset |
CAPTURE_OUTPUT |
Capture stdout/stderr | yes/no/on/off/true/false/0/1 |
yes |
API surface (capability map)
- Core
GET /ping,GET /health: liveness checks for the API itself.- Auth
POST /auth: issue Biscuit tokens; accepts Basic, form, JSON, or Bearer override whenAPI_TOKENmatches.- Instances
GET /instances: list instances with creation/last-seen metadata.POST /instances: register an instance (hostname/port/server_name/method).GET/PATCH/DELETE /instances/{hostname}: inspect, update mutable fields, or delete API-managed instances.DELETE /instances: bulk delete API-managed instances; non-API entries are skipped.- Health/actions:
GET /instances/ping,GET /instances/{hostname}/ping,POST /instances/reload?test=yes|no,POST /instances/{hostname}/reload,POST /instances/stop,POST /instances/{hostname}/stop. - Global settings
GET /global_settings: non-defaults by default; addfull=truefor all settings,methods=trueto include provenance.PATCH /global_settings: upsert API-owned globals; read-only keys are rejected.- Services
GET /services: list services (include drafts by default).GET /services/{service}: fetch non-defaults or full config (full=true);methods=trueincludes provenance.POST /services: create a service (draft or online), set variables, and updateSERVER_NAMEroster atomically.PATCH /services/{service}: rename, update variables, toggle draft.DELETE /services/{service}: remove service and derived config keys.POST /services/{service}/convert?convert_to=online|draft: switch draft/online quickly.- Custom configs
GET /configs: list snippets (default serviceglobal);with_data=trueembeds printable content.POST /configs,POST /configs/upload: create snippets via JSON or file upload.GET /configs/{service}/{type}/{name}: fetch snippet;with_data=truefor content.PATCH /configs/{service}/{type}/{name},PATCH .../upload: update or move API-managed snippets.DELETE /configsorDELETE /configs/{service}/{type}/{name}: remove API-managed snippets; template-managed entries are skipped.- Supported types:
http,server_http,default_server_http,modsec,modsec_crs,stream,server_stream, CRS/plugin hooks. - Bans
GET /bans: aggregate active bans from instances.POST /bansor/bans/ban: apply one or more bans; payload can be object, array, or stringified JSON.POST /bans/unbanorDELETE /bans: remove bans globally or per service.- Plugins (UI plugins)
GET /plugins: list plugins;with_data=trueincludes packaged bytes when available.POST /plugins/upload: install UI plugins from.zip,.tar.gz,.tar.xz.DELETE /plugins/{id}: remove a plugin by ID.- Cache (job artefacts)
GET /cache: list cache files with filters (service,plugin,job_name);with_data=trueembeds printable content.GET /cache/{service}/{plugin}/{job}/{file}: fetch/download a specific cache file (download=true).DELETE /cacheorDELETE /cache/{service}/{plugin}/{job}/{file}: delete cache files and notify scheduler.- Jobs
GET /jobs: list jobs, schedules, and cache summaries.POST /jobs/run: mark plugins as changed to trigger associated jobs.
Operational behaviour
- Error responses are normalized to
{"status": "error", "message": "..."}with appropriate HTTP status codes. - Write operations persist to the shared database; instances consume changes via scheduler sync or after a reload.
API_ROOT_PATHmust match the reverse-proxy path so/docsand links work correctly.- Startup exits if no authentication path exists (no Biscuit keys, no admin user, and no
API_TOKEN); errors are logged to/var/tmp/bunkerweb/api.error.