Synopsis #
A reverse proxy can give several internal web applications one controlled Internet-facing boundary. It centralizes public listeners, TLS certificates, request limits, and access logs, but it also becomes a shared dependency. Certificate automation and monitoring must be designed with the proxy rather than added after services are published.
This guide defines an architecture and qualification sequence for a home or small server. It is not a universal Caddy or nginx configuration. Application protocols, authentication, uploaded data, and WebSocket or streaming behavior determine the final directives.
The upstream application should first satisfy the network-service publication checklist . A reverse proxy changes the public entry point; it does not assume ownership of the application’s updates, secrets, durable data, authentication, or recovery.
Keep the public boundary narrow #
The usual design has four layers:
| Layer | Responsibility |
|---|---|
| DNS and network edge | Send each public name to the intended address and permit only required ports |
| Reverse proxy | Terminate TLS, select a virtual host, and forward to one reviewed upstream |
| Application boundary | Listen on a private address, loopback address, Unix socket, or isolated jail network |
| Monitoring path | Test from outside the host and deliver alerts through an independent channel |
Do not publish an application’s administrative port merely because its web interface is convenient. Bind the application to the narrowest reachable address and publish only the proxy. A jailed application can remain behind the host firewall using the principles in publish a Bastille service through PF .
The reverse proxy is not an authentication system unless an explicitly designed authentication component is integrated. Forwarded identity headers must be stripped from untrusted clients and set only by the trusted proxy path.
Choose Caddy or nginx by ownership model #
| Requirement | Caddy | nginx |
|---|---|---|
| Integrated public certificate automation | Built-in automatic HTTPS | Use a separate ACME client or certificate workflow |
| Small declarative reverse-proxy configuration | Strong fit | Strong fit with more explicit directives |
| Existing nginx operational knowledge and templates | Possible migration cost | Strong fit |
| Fine-grained established HTTP feature configuration | Capable; confirm required module | Broad nginx directive ecosystem |
| Certificate private-key ownership | Caddy-managed storage | External client and nginx file permissions |
Caddy documentation states that Automatic HTTPS obtains and renews certificates for qualifying site addresses and redirects HTTP to HTTPS. The behavior still depends on correct DNS, reachable ACME challenges, writable certificate storage, and a successful reload path.
nginx terminates HTTPS from configured certificate and private-key files. Certificate issuance and renewal remain a separate component, including a hook that validates and reloads nginx after renewal. Private keys must be readable by the required master process and inaccessible to application accounts.
Choose one owner for certificate storage and renewal. Running two ACME clients for the same names creates conflicting state and can exhaust issuance limits without improving availability.
Qualify one upstream before adding more #
Start with a noncritical application and record:
- public hostname and expected redirects;
- upstream address, port, protocol, and certificate trust if HTTPS is used internally;
- required request size and timeouts;
- WebSocket, streaming, or long-poll behavior;
- application expectation for
Hostand forwarded client information; - health endpoint that does not modify data;
- access-log retention and sensitive fields.
Keep an upstream on plain HTTP only when its private network boundary is trusted and documented. When proxying to an HTTPS upstream, validate its certificate against an explicit trust source. Disabling upstream TLS verification preserves encryption without authenticating the upstream and should not be presented as a secure fix.
Validate configuration before every reload. Then test the public hostname, certificate chain, redirect behavior, a representative authenticated action, upload limits, and application-generated absolute URLs.
Treat certificate renewal as production work #
Certificate automation needs more than a timer. It needs:
- an issuance method compatible with DNS and inbound firewall policy;
- persistent state backed up according to the tool’s documentation;
- restricted private-key access;
- a staging or dry-run path where the ACME client provides one;
- configuration validation and service reload after renewal;
- an external expiry alert that remains useful when renewal stops.
DNS challenge credentials can often modify an entire zone. Restrict them to the narrowest supported scope and keep them outside application jails and source repositories. HTTP challenge handling should expose only the required path and should not bypass application authorization rules more broadly.
Monitor certificate validity from outside the server. A local file can be current while DNS points elsewhere or the proxy serves a different virtual host.
Establish minimum actionable monitoring #
A small deployment does not require a large observability stack, but it does require independent answers to the following questions:
| Check | Failure represented |
|---|---|
| External HTTPS request by hostname | DNS, routing, firewall, TLS, proxy, and basic application path |
| Certificate expiry and hostname match | Renewal or virtual-host failure before browsers reject it |
| Application-specific health check | A running proxy with an unusable upstream |
| Disk and ZFS pool capacity | Impending write failure and uncontrolled snapshot or log growth |
zpool status health | Device or data-integrity degradation |
| Backup age and last restore test | Lost recovery capability despite healthy production |
| Package vulnerability and update status | Known exposure and maintenance backlog |
| Authentication and error-rate summary | Abuse, brute-force activity, or application regression |
Every check needs an owner, threshold, alert destination, and response note. Alerts stored only on the failed host are not alerts. Rate-limit repetitive failures so a network outage does not bury the first useful event.
Use service, sockstat, logs, and application health endpoints to diagnose local failures. Process existence alone is insufficient: a service can run while returning errors or pointing to an unavailable database.
Protect logs and application data #
Proxy logs can contain client addresses, query strings, user agents, and application paths. Avoid logging authorization headers and sensitive query parameters. Set rotation, retention, and permissions deliberately; unbounded logs can take down every proxied service by filling the filesystem.
Keep application data in separate datasets or filesystems with independent capacity and backup policy. A reverse proxy configuration backup cannot restore an application database. Plan a FreeBSD home server covers failure domains, and choose NFS or Samba covers a separate file-sharing boundary.
Make proxy failure recoverable #
Retain the last validated proxy configuration and the package version that accepted it. Test rollback before the proxy carries several services. A failed change should restore the last configuration, validate it, reload the service, and re-run the external health check.
Avoid coupling all maintenance actions. Proxy package upgrades, certificate-client upgrades, application upgrades, DNS changes, and firewall changes should not occur in one unreviewable event. Configuration management can stage these changes using the Ansible or Salt operating guide .