Skip to main content

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​

OptionTypeDescriptionDefault
cs_rp_nginx_proxy_manager_clusterstringCluster name for the proxy{{ cs_cluster_name }}
cs_rp_nginx_proxy_manager_directorystringDirectory for data and configuration/app/nginx-proxy-manager
cs_rp_nginx_proxy_manager_cluster_dnslist[string]DNS servers for the containerThe cluster's DNS servers (from Vault)
cs_rp_nginx_proxy_manager_versionstringNginx Proxy Manager version2.16.0
cs_rp_nginx_proxy_manager_docker_imagestringNginx Proxy Manager docker image{{ cs_vm_artifact_registry_containers_home }}/nginx-proxy-manager
cs_rp_nginx_proxy_manager_access_list_policy_namestringName of the managed access listHome Access

cs_rp_hosts.<Application Name>​

Host definitions come from cs_rp_hosts (see Reverse Proxy), defined per host in inventory.yml.

OptionTypeDescriptionApplied
domainslist[string]Domains for the applicationYes
forward_hoststringApplication hostname or IPYes
forward_portintApplication portYes
forward_schemestringhttp or httpsYes
caching_enabledboolEnable caching (default true)Yes
http2_supportboolEnable HTTP/2 support (default true)Yes
hsts_enabledboolEnable HSTS (default true)Yes
hsts_subdomainsboolEnable HSTS for subdomains (default true)Yes
block_exploitsboolBlock common exploits (default true)Yes
allow_websocket_upgradeboolAllow websocket upgrade (default false)Yes
ssl_forcedboolForce SSL (default true)Yes
enabledboolEnable the application (default true)Yes
mtls_outboundboolEnable 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/, and CAs.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 plain reverse_proxy tag.

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