Proxbox Backend Setup¶
The NetBox plugin requires a separate proxbox-api FastAPI service. The plugin stores configuration in NetBox, but sync requests are sent to this backend.
Backend Role¶
The current plugin code expects a configured FastAPIEndpoint object and uses it for:
- device sync
- virtual machine sync
- full update
- VM backup sync
Option 1: Install The Backend With pip¶
mkdir -p /opt/proxbox-api
cd /opt/proxbox-api
python3 -m venv venv
source venv/bin/activate
pip install --upgrade proxbox-api
Start it manually:
/opt/proxbox-api/venv/bin/uvicorn proxbox_api.main:app --host 0.0.0.0 --port 8800 --app-dir /opt/proxbox-api
Option 2: Run The Backend In Docker¶
docker pull emersonfelipesp/proxbox-api:latest
docker run -d --name proxbox-api -p 8800:8000 emersonfelipesp/proxbox-api:latest
The image serves on container port 8000 (nginx), so keep 8800:8000 if you want the backend reachable as http://<host>:8800.
If you want NetBox to connect over HTTPS, use the TLS (*-nginx) image instead:
docker pull emersonfelipesp/proxbox-api:latest-nginx
docker run -d --name proxbox-api-tls \
-p 8800:8000 \
-e MKCERT_EXTRA_NAMES='proxbox.backend.local' \
emersonfelipesp/proxbox-api:latest-nginx
When configuring the NetBox FastAPIEndpoint for an *-nginx (TLS-only) image,
set:
| Field | Value | Why |
|---|---|---|
| Use HTTPS | ✓ enabled | The image only listens on TLS; plain HTTP returns 400. |
| Verify SSL | ✗ disabled (when using the bundled mkcert cert) | The cert is self-signed; verification will fail unless the mkcert root CA is trusted on the NetBox host. Tick only if you have installed the mkcert CA into NetBox's trust store. |
| Port | host port mapped to container 8000 |
Typically 8800. |
Use HTTPS and Verify SSL are independent toggles since v0.0.15 (issue #352).
Earlier releases coupled them, which made the *-nginx + self-signed-cert combo
unreachable from the UI.
Custom certificates¶
If you supply your own CA-signed, Let's Encrypt, or corporate certificates,
the *-nginx and *-granian images detect them automatically and skip
mkcert generation. Mount the certificate directory read-only:
docker run -d --name proxbox-api-tls \
-p 8800:8000 \
-v /path/to/certs:/certs:ro \
emersonfelipesp/proxbox-api:latest-nginx
The /certs directory must contain cert.pem and key.pem. If either file is
absent, mkcert auto-generation runs as normal.
When using a publicly trusted or internally-trusted certificate (not self-signed), you can enable certificate verification on the FastAPIEndpoint:
| Field | Value |
|---|---|
| Use HTTPS | ✓ enabled |
| Verify SSL | ✓ enabled |
| Port | host port mapped to container 8000 |
For the granian image, see the full certificate handling notes in the proxbox-api installation docs.
Option 3: Run It As A systemd Service¶
This repository includes sample service files:
contrib/proxbox.servicecontrib/proxbox-https.service
Install one of them:
sudo cp -v /opt/netbox/netbox/netbox-proxbox/contrib/proxbox.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now proxbox
sudo systemctl status proxbox
The sample unit expects the backend virtual environment under /opt/proxbox-api/venv and starts:
/opt/proxbox-api/venv/bin/uvicorn proxbox_api.main:app --host 0.0.0.0 --port 8800 --app-dir /opt/proxbox-api
Adjust the service file if your backend lives somewhere else.
Option 4: Run The Backend From Source¶
Use the source workflow when you want the latest backend code or need to patch the backend itself:
cd /opt
git clone https://github.com/emersonfelipesp/proxbox-api.git
cd /opt/proxbox-api
python3 -m venv venv
source venv/bin/activate
pip install -e .
/opt/proxbox-api/venv/bin/uvicorn proxbox_api.main:app --host 0.0.0.0 --port 8800 --app-dir /opt/proxbox-api
TLS Notes¶
If you terminate TLS in front of uvicorn with nginx, keep the proxy streaming-friendly:
proxy_passto the localuvicornprocess on127.0.0.1:8000- set
proxy_http_version 1.1 - forward
Host,X-Real-IP,X-Forwarded-For, andX-Forwarded-Proto - disable buffering with
proxy_buffering off - keep long read/send timeouts so SSE
/streamresponses are not cut off early
For a complete nginx example, see the backend repository README and the bundled nginx templates under docker/nginx/.
If you use the HTTPS sample unit and point it at NetBox-managed certificates, the backend process may need permission to read them:
sudo chmod +rx -R /etc/ssl/private/
sudo chmod +rx -R /etc/ssl/certs/
That is convenient, but you should review the security impact for your environment before using it.
Authentication¶
The NetBox plugin and proxbox-api backend use database-backed API key authentication:
Fail-closed automatic token setup¶
The backend token field is optional. Saving an enabled FastAPIEndpoint
without a token first records a fail-closed pending state. Automatic discovery
then uses the exact URL/IP, port, and TLS-verification policy saved through the
NetBox UI as its allowlist. It performs credential-free / and /health
checks, with redirects disabled, before either of these flows:
- Locally held key — authenticate the already encrypted key with one
read-only
GET /auth/keysrequest. This repairs legacy blank fingerprints, activation, and configured target changes without asking the operator to paste the same secret again. - Uninitialized backend (
needs_bootstrap=true) — generate a strong key, retain it encrypted in NetBox, and require one successfulPOST /auth/register-key(201).
An initialized backend never receives the bootstrap POST. If it rejects the locally held key, or NetBox has no recoverable key, the row remains pending and runtime traffic stays blocked. The backend never exposes existing raw keys.
When no FastAPIEndpoint row exists, startup discovery is bounded to an
explicit PLUGINS_CONFIG["netbox_proxbox"]["backend_url"] value (with optional
boolean backend_verify_ssl, default true) and same-site
backend.proxbox.<domain> / legacy proxbox.backend.<domain> names derived
from NetBox's configured public origin. There is no network or subnet scan.
After an endpoint is saved in the UI, that exact persisted target becomes the
complete discovery allowlist; another IP or domain is rejected unless the
operator first saves it as the endpoint configuration.
Authentication rejection, conflict, throttling, timeout, TLS failure, or a connection error leaves the previous encrypted token unchanged and the target untrusted. A disabled endpoint never makes a connection and a new disabled row remains keyless. The identity, bootstrap-status, key-list, and registration checks do not follow redirects, so credentials cannot be forwarded to another origin.
After adoption, the plugin stores a credential-free SHA-256 target fingerprint covering the canonical primary HTTP authority, fallback IP, port, HTTP/TLS flags, and WebSocket authority flags. Runtime callers recompute it before using the stored key and fail closed on target drift. The WebSocket client also disables ambient proxies, refuses a server-selected redirect before sending the key, rechecks trust after its handshake and periodically while connected, and is cancelled when the endpoint changes.
The generated first key is encrypted before it becomes available to runtime callers and is never logged or rendered. If the backend becomes initialized by another actor during bootstrap, the conflict remains pending rather than being treated as proof of authentication.
See Endpoint Auto-Configuration for the complete state machine, operator outcomes, requirements-to-tests matrix, and branch-coverage gate.
Manual Token Management¶
The diagnostic command never prints token fragments. Disabled endpoints are
always skipped, including under --fix. Without --fix the command is
read-only and does not contact an unadopted legacy target. After the operator
reviews the target, --fix authenticates the durably stored key and records its
target fingerprint; it performs a one-time bootstrap only when the backend
confirms that no key exists:
# Check token status
python manage.py proxbox_fix_tokens
# Fix unregistered tokens
python manage.py proxbox_fix_tokens --fix
Safe key rotation¶
Keep the current key active until the replacement has been adopted and verified:
# 1. Authenticate with the current key and create a replacement.
curl -X POST http://localhost:8800/auth/keys \
-H "X-Proxbox-API-Key: current-key"
The response exposes raw_key exactly once. Copy it directly into the existing
NetBox FastAPIEndpoint token field. Saving performs a protected read with the
candidate before changing the encrypted database value. Verify another
protected backend request from the plugin, then deactivate or delete the old
key. If any step fails, keep the old key active and retry only after resolving
the reported condition. Automatic generation is limited to first-key bootstrap;
it is never used as a rotation mechanism.
The unauthenticated POST /auth/register-key route is only for a backend whose
bootstrap status explicitly reports that it has no keys. It is never a rotation
mechanism, and HTTP 409 is a failure rather than proof that a candidate works.
Key Management¶
After the first key is registered, manage keys via the authenticated API:
# List keys (requires auth)
curl http://localhost:8800/auth/keys \
-H "X-Proxbox-API-Key: your-key"
# Create a new key
curl -X POST http://localhost:8800/auth/keys \
-H "X-Proxbox-API-Key: your-key"
# Delete a key
curl -X DELETE http://localhost:8800/auth/keys/1 \
-H "X-Proxbox-API-Key: your-key"
Credential Encryption Key (required before the first Proxmox endpoint)¶
Do this before creating the Proxmox endpoint. The backend refuses to store any Proxmox credential until it has an encryption key, and it says so only at the moment you try:
Credential encryption is not configured, so this secret cannot be stored.
Set PROXBOX_ENCRYPTION_KEY, configure the ProxboxPluginSettings 'encryption_key' field,
create a local key via POST /admin/encryption/key, or set PROXBOX_ALLOW_PLAINTEXT_CREDENTIALS=1
That message lists every option; it simply arrives after you believe setup is finished.
Generate a key¶
python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
This is the same value generate_encryption_key() produces inside the backend: a
canonical URL-safe base64 Fernet key. Credentials are encrypted with Fernet
(AES-128-CBC with HMAC), and the encrypted fields are the NetBox endpoint token and
token key plus the Proxmox endpoint password and token value.
Supply it to the backend¶
Pick one source. PROXBOX_ENCRYPTION_KEY is the recommended one because the key then
lives with the backend that uses it.
docker run -d --name proxbox-api \
-p 8800:8000 \
-e PROXBOX_ENCRYPTION_KEY='<paste the generated key>' \
emersonfelipesp/proxbox-api:latest
# /etc/systemd/system/proxbox-api.service
[Service]
Environment=PROXBOX_ENCRYPTION_KEY=<paste the generated key>
Then systemctl daemon-reload && systemctl restart proxbox-api.
Have the backend generate a key and persist it to its own key file. The response returns the key once — store it somewhere safe, because losing it strands every credential encrypted with it:
curl -X POST http://localhost:8800/admin/encryption/generate \
-H "X-Proxbox-API-Key: your-key"
To persist a key you generated yourself instead, POST /admin/encryption/key takes
it in the body:
curl -X POST http://localhost:8800/admin/encryption/key \
-H "X-Proxbox-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"key": "<paste the generated key>"}'
GET /admin/encryption/status reports whether a key is configured and which source
it came from (env, plugin, or local).
In Docker, set PROXBOX_ENCRYPTION_KEY_FILE as well
The key file defaults to data/encryption.key next to the installed
package — inside the container that is /app/data/encryption.key, which is
not on the image's declared /data volume. Recreating the container therefore
destroys the key while the database on /data survives, stranding every
credential encrypted with it. Point the key at the volume:
-e PROXBOX_ENCRYPTION_KEY_FILE=/data/encryption.key
PROXBOX_ENCRYPTION_KEY has no such problem, which is one more reason to prefer
it for containerised deployments.
The remaining option, PROXBOX_ALLOW_PLAINTEXT_CREDENTIALS=1, stores Proxmox passwords
and tokens unencrypted in the backend's SQLite database. It exists for local
experimentation; do not use it anywhere real.
This is not the same key as the plugin's
Proxbox has three separate key domains and they must never be cross-copied: the proxbox-api-at-rest key covered here, the plugin-at-rest Fernet key that protects encrypted fields in NetBox's own database, and the FastAPI endpoint API key that authenticates HTTP requests. See Plugin Settings — Three separate security domains for the full comparison.
What happens if the key changes¶
Fernet ciphertext can only be decrypted by the key that produced it, so changing the key without re-encrypting makes every stored credential unreadable. Both key domains are protected against that, and neither silently discards data:
Plugin-at-rest key (NetBox database). Rotation is supported and verified. Ordinary
form saves, model saves, and API PATCH requests cannot replace the key while any
registered ciphertext exists; direct QuerySet.update() / bulk_update() / upsert writes
to the key are rejected outright. Use Verified plugin key rotation on the settings
page: it locks the settings row and every registered ciphertext table, verifies every
non-empty ciphertext against the current key, and only then re-encrypts everything and
stores the new key — all in one transaction. A wrong current key, or a single corrupt row,
aborts the whole thing without changing any ciphertext or any setting. If the old key is
genuinely lost, a separate netbox_proxbox.reset_encrypted_secrets permission gates a
destructive reset that clears the affected secrets so they can be re-entered; there is
no way to recover the plaintext without the key.
proxbox-api-at-rest key (backend SQLite). Re-point PROXBOX_ENCRYPTION_KEY and
re-encrypt the backend database before restarting. Verified plugin rotation additionally
requires every enabled, adopted, operational backend to return a successful authenticated
GET /admin/encryption/status attestation confirming that its active key decrypts every
stored credential — so a backend left on the old key blocks the plugin-side rotation
rather than being silently stranded.
Full detail: Plugin Settings — Verified rotation and lost-key reset.
Next Step In NetBox¶
After the backend is reachable, create these objects in the Proxbox UI:
Proxmox APINetBox APIProxBox API (FastAPI)
Then return to Plugins > Proxbox and run Full Update.