Reverse Proxy: Nginx Proxy Manager
Deploy Nginx Proxy Manager server. See
Reverse Proxy for the implementation-agnostic overview, the shared variables, and
the cs_rp_which_one switch that selects Nginx Proxy Manager (the default).
Ansible hosts group: reverse_proxy
Variables: shared
Shared variables are documented on the Reverse Proxy page.
Variables
| Option | Type | Description | Default |
|---|---|---|---|
cs_rp_nginx_proxy_manager_cluster | string | Cluster name for the proxy | {{ cs_cluster_name }} |
cs_rp_nginx_proxy_manager_directory | string | Directory for data and configuration | /app/nginx-proxy-manager |
cs_rp_nginx_proxy_manager_cluster_dns | list[string] | DNS servers for the container | The cluster's DNS servers (from Vault) |
cs_rp_nginx_proxy_manager_version | string | Nginx Proxy Manager version | 2.16.0 |
cs_rp_nginx_proxy_manager_docker_image | string | Nginx Proxy Manager docker image | {{ cs_vm_artifact_registry_containers_home }}/nginx-proxy-manager |
cs_rp_nginx_proxy_manager_access_list_policy_name | string | Name of the managed access list | Home Access |
cs_rp_hosts.<Application Name>
Host definitions come from cs_rp_hosts (see
Reverse Proxy), defined per host in inventory.yml.
| Option | Type | Description | Applied |
|---|---|---|---|
domains | list[string] | Domains for the application | Yes |
forward_host | string | Application hostname or IP | Yes |
forward_port | int | Application port | Yes |
forward_scheme | string | http or https | Yes |
caching_enabled | bool | Enable caching (default true) | Yes |
http2_support | bool | Enable HTTP/2 support (default true) | Yes |
hsts_enabled | bool | Enable HSTS (default true) | Yes |
hsts_subdomains | bool | Enable HSTS for subdomains (default true) | Yes |
block_exploits | bool | Block common exploits (default true) | Yes |
allow_websocket_upgrade | bool | Allow websocket upgrade (default false) | Yes |
ssl_forced | bool | Force SSL (default true) | Yes |
enabled | bool | Enable the application (default true) | Yes |
mtls_outbound | bool | Enable mTLS for outbound traffic (default true) | Yes |
Vault configurations
- key:
{{ cs_project_code }}/application-deployer/clusters/{{ Lab Name or Cluster name }}/hosts/{{ hostname }}/apps/reverse-proxy/config
Default certificates, credentials, and optional per-application certificate overrides are read
from Vault; proxy host definitions themselves come from cs_rp_hosts (see above).
{
"certificate": "Mandatory. Default certificate for the reverse proxy.",
"private_key": "Mandatory. Default private key for the reverse proxy.",
"admin_user": "admin@example.com",
"admin_password": "password",
"management_port": "<Int port for management console.>",
"apps": {
"<Application Name, matching a key in cs_rp_hosts>": {
"certificate": "Optional. Certificate for the application.",
"private_key": "Optional. Private key for the certificate."
}
}
}
Top-level certificate and private_key are mandatory default certificates for the reverse
proxy. If certificate/private_key are not provided for an application in apps, the default
top-level certificate and private_key are used.
Access list
On every run, the play looks up the access list named cs_rp_nginx_proxy_manager_access_list_policy_name via the Nginx Proxy
Manager API. A jmespath query filters the results by name and fails the run if more
than one access list shares that name. If it finds a match, it updates that access list in place
using the payload built from cs_rp_cidr_allow_list; otherwise it creates a new access
list from the same payload. Either way, the play keeps that access list's ID and attaches it to
every proxy host it configures.
Proxy host cleanup
On every run, the play fetches all existing proxy hosts from the Nginx Proxy Manager API and
deletes each one whose domain_names overlaps with at least one domain configured
across cs_rp_hosts.*.domains, leaving proxy hosts with no overlap untouched. It then
(re)creates the remaining proxy hosts from cs_rp_hosts.
Certificates and mutual TLS
For each application in cs_rp_hosts, the play finds the custom certificate whose name matches the
application name (creating it for the application's domains when none exists, and failing if more
than one matches), validates the application's certificate and private key from Vault (falling back
to the default pair), and uploads them to it. When mtls_outbound is true (the default), the proxy
host also gets an advanced configuration that requires a client certificate signed by the root CA,
which the play copies to {{ cs_rp_nginx_proxy_manager_directory }}/CAs.pem.
Admin password reset and health check
On every run, the play marks any existing admin user row in the SQLite database as deleted, then
starts a throwaway container with INITIAL_ADMIN_EMAIL/INITIAL_ADMIN_PASSWORD set from
Vault (admin_user/admin_password) to recreate the
admin user, and removes that container once it's healthy. It then starts the long-running
container without those environment variables. This keeps the admin credentials in sync with Vault on every
deployment.
After the long-running container starts, the play polls the management API at
http://127.0.0.1:<management port>/api/
(up to 100 retries, 3s apart) until it returns HTTP 200, then fails the run unless the response
body is {"status": "OK"}.
Restic backup and restore
Backups cover the whole {{ cs_rp_nginx_proxy_manager_directory }} directory: data/,
letsencrypt/, logs/, and CAs.pem, using the
restic_backup_restore_sftp
module. The {{ cs_rp_container_name }} container stops for the duration of the backup and
starts again afterwards, even if the backup itself fails.
The restic_setup_repositories stage generates Restic repository credentials per node/repo; the
play reads them from Vault at
{{ cs_project_code }}/application-deployer/clusters/{{ cs_rp_restic_cluster_name }}/hosts/{{ cs_rp_restic_node_name }}/apps/restic/generated/sftp-repository-access-{{ cs_rp_restic_repo_name }}
(and the matching sftp-repository-password-... key). The repository name
(cs_rp_restic_repo_name, see Reverse Proxy for its default) must
be present in cs_restic_repos on the host running the restic repository.
Restore removes the {{ cs_rp_container_name }} container, restores the latest snapshot into a
temporary directory under /app/tmp (restores can exceed 50GB, too large for the default
/tmp), then rsyncs it into {{ cs_rp_nginx_proxy_manager_directory }}. Restore
does not recreate the container: run reverse_proxy_install afterwards to bring it back up with
the restored data.
Cleanup (dangerously_cleanup_reverse_proxy)
Destructive. Only runs when this exact tag is passed explicitly (it is intentionally excluded from the
plain reverse_proxy tag) and permanently deletes Nginx Proxy Manager's own data on the host:
- Removes the
{{ cs_rp_container_name }}Docker container. - Deletes the UFW allow rules opened for ports 80/tcp, 443/tcp, and
management_port. - Deletes
{{ cs_rp_nginx_proxy_manager_directory }}(data/,letsencrypt/,logs/, andCAs.pem).
Tags
reverse_proxy: Deploy Nginx Proxy Manager and run its Restic backup.reverse_proxy_install: Deploy Nginx Proxy Manager only.reverse_proxy_restic_backup: Run Restic backup for Nginx Proxy Manager.reverse_proxy_restic_restore: Restore Nginx Proxy Manager's data from the latest Restic snapshot.dangerously_cleanup_reverse_proxy: Destructive. Removes the Nginx Proxy Manager Docker container, closes its UFW ports, and deletes{{ cs_rp_nginx_proxy_manager_directory }}. Never included by the plainreverse_proxytag.
Deployment
uv sync --all-extras --all-packages --no-progress
uv --offline run --no-sync --no-progress ansible-galaxy install -r requirements.yml
uv --offline run --no-sync --no-progress ansible-playbook playbook.yml --tags reverse_proxy_install