Admin API

The admin API includes a built-in control plane with health checks, server status, configuration inspection, and remote reload capability. It runs on a separate HTTP listener from your web server. Treat it with the same security as a root shell on your server.

Warning

The admin API is not encrypted and has no authentication by default. You can enable bearer token authentication with the auth_token directive. All other security relies on network-level isolation (binding to 127.0.0.1 or using infrastructure firewalls).

Secure default configuration⁠#

The recommended default binds the admin API to the loopback interface and enables only the endpoints you need:

{
    admin {
        listen "127.0.0.1:8081"

        health true
        status false
        config false
        reload true
        reload_get true
        runtime true
    }
}

This configuration is safe for most deployments because 127.0.0.1 is only reachable from the local machine.

Health checks in containers and orchestration⁠#

Use the health endpoint for liveness or readiness probes when running inside a container or an orchestrator. Running the server normally returns 200 OK. Shutdown returns 503 Service Unavailable.

{
    admin {
        listen "127.0.0.1:8081"

        health true
        status false
        config false
        reload false
        reload_get false
        runtime false
    }
}

With 127.0.0.1, the health check works for any process running on the same host. This includes container orchestrators that inspect the container network namespace.

Accessing the admin API from a remote machine⁠#

Even with auth_token configured, you should never expose the admin API directly to untrusted networks. Use one of the following patterns instead.

SSH tunneling is the simplest and most secure way to access the admin API remotely:

ssh -L 8081:127.0.0.1:8081 admin@your-server

After you establish the tunnel, open http://127.0.0.1:8081 in your browser or use curl:

curl http://127.0.0.1:8081/health
curl http://127.0.0.1:8081/status
curl http://127.0.0.1:8081/config
curl -X POST http://127.0.0.1:8081/reload
curl http://127.0.0.1:8081/reload
curl http://127.0.0.1:8081/runtime

The SSH connection gives you encryption and authentication. This compensates for the lack of TLS in the admin API itself.

Reverse proxy with authentication⁠#

For more complex setups, you can front the admin API with an authenticating reverse proxy:

Remote user → Reverse Proxy (basic auth / OAuth) → 127.0.0.1:8081 (admin API)

Example proxy configuration (Ferron):

admin.example.com {
    proxy http://127.0.0.1:8081

    basic_auth {
        realm "Admin API"
        users {
            alice "$argon2id$v=19$m=19456,t=2,p=1$..."
        }
    }
}

With this pattern, the admin API remains bound to 127.0.0.1. The proxy handles TLS termination and authentication.

Managing endpoints by role⁠#

You can adjust the admin API configuration to the operator role by enabling only the necessary endpoints.

Read-only operator (monitoring only)⁠#

For operators who only need to check server status:

{
    admin {
        listen "127.0.0.1:8081"

        health true
        status true
        config true
        reload false
        reload_get false
        runtime false
    }
}

DevOps operator (full control)⁠#

For operators who need to manage the server:

{
    admin {
        listen "127.0.0.1:8081"

        health true
        status true
        config true
        reload true
        reload_get true
        runtime true
    }
}

Minimal operator (health check only)⁠#

For deployments where only health monitoring matters:

{
    admin {
        listen "127.0.0.1:8081"

        health true
        status false
        config false
        reload false
        reload_get false
        runtime false
    }
}

Security hardening checklist⁠#

  • Set the listen address to 127.0.0.1 (or another loopback address).
  • Disable unnecessary endpoints (status false, config false, reload false, reload_get false, runtime false).
  • Make sure no firewall rule allows external traffic to the admin port.
  • Use SSH tunneling or an authenticating reverse proxy for remote access.
  • Never bind the admin API to 0.0.0.0 or a public IP address.
  • Do not publish the admin port to the host network when the server runs in a container.

Troubleshooting⁠#

“Connection refused” when accessing the admin API locally⁠#

Verify that the admin listener is actually running and bound to the expected address:

ss -tlnp | grep 8081

If the command shows nothing, check the Ferron logs for startup errors. The admin block must be present in your configuration for the listener to start.

“Connection refused” from a remote machine⁠#

The admin API is not designed for remote access. Use SSH tunneling or a reverse proxy as described in Accessing the admin API from a remote machine.

GET /reload returns an error⁠#

The GET /reload endpoint requires reload_get true in the admin configuration. If it returns an error, verify that you enabled the endpoint.

POST /reload returns an error⁠#

The POST /reload endpoint requires reload true in the admin configuration. If it returns an error, verify that you enabled the endpoint and that the new configuration file is valid. Use ferron validate -c ferron.conf before reloading to catch configuration errors.

GET /config shows redacted values⁠#

Ferron automatically redacts sensitive values (TLS keys, passwords, tokens, bearer credentials, and htpasswd entries) and replaces them with "[redacted]". You cannot disable this. The redacted configuration still helps audit routing rules, upstream addresses, and host configuration.

Admin API stops responding after a configuration change⁠#

Ferron shuts down and restarts the admin listener during every configuration reload. If the new configuration is valid, a fresh listener starts immediately. If you remove the admin block from the new configuration, the listener stops permanently.

Accidentally bound to 0.0.0.0⁠#

If you have bound the admin API to 0.0.0.0, change it to 127.0.0.1 immediately and reload:

{
    admin {
        listen "127.0.0.1:8081"
        health true
        status false
        config false
        reload true
        reload_get false
        runtime false
    }
}

Then verify the change took effect:

ss -tlnp | grep 8081

The output should show 127.0.0.1:8081, not 0.0.0.0:8081.

See also⁠#