Self-hosted • Privacy-first • No tracking
Home / Homelab / Nginx Proxy Manager Setup: The Complete Homelab Guide for Secure Reverse Proxying
Homelab #homelab#docker-compose#nginx-proxy-manager#reverse-proxy#ssl ⏱ 11 min • 👁 4 • Aug 29, 2026

Nginx Proxy Manager Setup: The Complete Homelab Guide for Secure Reverse Proxying

Step-by-step guide to install and configure Nginx Proxy Manager in your homelab using Docker Compose, with SSL, security hardening, and troubleshooting.

AdSense — Top (970x90) • Responsive
Nginx Proxy Manager Setup: The Complete Homelab Guide for Secure Reverse Proxying

Introduction

In any homelab, exposing multiple web services (like Grafana, Nextcloud, or a self-hosted AI dashboard) to your local network or the internet requires a robust reverse proxy. Manually configuring Nginx is powerful but error-prone, especially when you have dozens of services and need SSL certificates for each. This is where Nginx Proxy Manager (NPM) shines—it provides a clean web UI to manage reverse proxy hosts, automatic Let's Encrypt SSL certificates, and access lists, all without touching a single Nginx config file.

This guide is written for homelab enthusiasts who have basic Docker knowledge but want a production-ready setup. You will learn how to deploy NPM using Docker Compose, configure your first proxy host, enable SSL with Let's Encrypt, and apply security hardening. We'll also cover common pitfalls like port conflicts and database permission errors, with actionable solutions.

By the end of this guide, you'll have a fully functional reverse proxy that centralizes access to all your homelab services, complete with HTTPS and fine-grained access control. The entire process takes about 30 minutes, and every command is copy-paste ready.

Prerequisites

Before you begin, ensure your homelab server meets the following minimum requirements. NPM is lightweight, but your underlying services will consume more resources.

Component Minimum Requirement Recommended
CPU 1 core 2+ cores
RAM 512 MB free 1 GB free
Storage 2 GB free 10 GB free (for logs and backups)
OS Ubuntu 20.04+, Debian 11+, or any Linux with Docker Same as minimum
Docker Engine 20.10+ Latest stable
Docker Compose v2 (plugin or standalone) Latest stable
Network Open ports 80 and 443 (for SSL) Static IP or domain name

Software prerequisites:

  • Docker and Docker Compose installed. If not, run:
    curl -fsSL https://get.docker.com | sh
    sudo usermod -aG docker $USER
    newgrp docker
    
  • A domain name (or subdomain) pointing to your server's public IP if you want internet-facing SSL. For local-only, you can use self-signed or skip SSL.
  • Basic familiarity with the command line and Docker concepts.

Step-by-Step Installation

Step 1: Create a Dedicated Directory

Create a directory to hold NPM's configuration and data. Using a dedicated path makes backups and upgrades easier.

mkdir -p ~/nginx-proxy-manager
cd ~/nginx-proxy-manager

Step 2: Create Docker Compose File

Create a docker-compose.yml file with the following content. This file defines the NPM service, its ports, and persistent volumes.

version: '3.8'

services:
  npm:
    image: 'jc21/nginx-proxy-manager:latest'
    container_name: npm
    restart: unless-stopped
    ports:
      - '80:80'          # HTTP
      - '443:443'        # HTTPS
      - '81:81'          # Admin UI
    environment:
      - DB_MYSQL_HOST=db
      - DB_MYSQL_PORT=3306
      - DB_MYSQL_USER=npm
      - DB_MYSQL_PASSWORD=npm_secret_change_this
      - DB_MYSQL_NAME=npm
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
    networks:
      - npm_network
    depends_on:
      - db

  db:
    image: 'mariadb:10.6'
    container_name: npm-db
    restart: unless-stopped
    environment:
      - MYSQL_ROOT_PASSWORD=root_secret_change_this
      - MYSQL_DATABASE=npm
      - MYSQL_USER=npm
      - MYSQL_PASSWORD=npm_secret_change_this
    volumes:
      - ./mysql:/var/lib/mysql
    networks:
      - npm_network

networks:
  npm_network:
    driver: bridge

Important: Change the passwords in the environment variables to strong unique values. The data and letsencrypt folders will store NPM's configuration and SSL certificates, respectively.

Step 3: Start the Stack

Launch the containers in detached mode. This will pull the images and start the services.

docker compose up -d

Verify that both containers are running:

docker ps

You should see npm and npm-db in the list, both with status Up.

Step 4: Access the Admin UI

Open your browser and go to http://your-server-ip:81. The default login credentials are:

  • Email: admin@example.com
  • Password: changeme

You will be forced to change the password on first login. Use a strong password and store it in a password manager.

Step 5: Change Default Admin Credentials

After logging in, go to Settings > Administration and change the email and password. Also, update the default user's name to something identifiable.

Step 6: Configure Your First Proxy Host

Suppose you have a service running on port 3000 (e.g., Grafana). To proxy it:

  1. In the NPM admin UI, go to Hosts > Proxy Hosts.
  2. Click Add Proxy Host.
  3. In the Details tab:
    • Domain Names: Enter grafana.yourdomain.com (or localhost if testing locally).
    • Scheme: http
    • Forward Hostname / IP: 192.168.1.100 (or the container name if on the same Docker network, e.g., grafana).
    • Forward Port: 3000
    • Cache Assets: Optional, enable for static files.
    • Block Common Exploits: Recommended to enable.
    • Websockets Support: Enable if the service uses WebSockets (e.g., Jupyter).
  4. Click Save.

Step 7: Enable SSL with Let's Encrypt

To secure your proxy host with a valid SSL certificate:

  1. Go to Hosts > Proxy Hosts and click the Edit icon on your newly created host.
  2. Switch to the SSL tab.
  3. Select Request a new SSL Certificate.
  4. Check Force SSL and HTTP/2 Support.
  5. Enter your email address for Let's Encrypt notifications.
  6. Click Save. NPM will automatically obtain and renew the certificate.

If you are using a local domain or IP, you can use the Self-Signed option, but browsers will show warnings.

Step 8: Set Up Access Lists (Optional)

To restrict access to certain services, create an access list:

  1. Go to Access Lists > Add Access List.
  2. Give it a name (e.g., home-only).
  3. Under Access Rules, click Add Rule.
  4. Choose Allow or Deny, and enter an IP range or a specific IP (e.g., 192.168.1.0/24).
  5. Save the list.
  6. Edit your proxy host and assign this access list under the Access List dropdown in the Details tab.

Step 9: Test the Proxy

Open your browser and navigate to https://grafana.yourdomain.com (or http://localhost:81 if you used localhost). You should see your service. If not, check the logs in NPM by running:

docker logs npm

Step 10: Verify SSL Renewal

Let's Encrypt certificates are valid for 90 days. NPM handles renewal automatically, but you can verify by checking the certificate expiry in the UI under Hosts > Proxy Hosts > Edit > SSL. The certificate details show the expiry date.

Step 11: Backup Configuration

Regularly back up the data and letsencrypt folders. You can also use the built-in backup feature in NPM (Settings > Backup) to download a JSON backup. For a full backup, stop the stack and copy the folders:

docker compose down
cp -r ./data ./backups/data_$(date +%Y%m%d)
cp -r ./letsencrypt ./backups/letsencrypt_$(date +%Y%m%d)
docker compose up -d

Step 12: Update NPM

To update to the latest version, simply pull the new image and recreate the containers:

docker compose pull
 docker compose up -d

This preserves your configuration and certificates.

Advanced Configuration and Optimization

Reverse Proxy for Docker Containers on the Same Network

If your services are running in Docker on the same network as NPM, you can use the container name as the forward hostname instead of an IP. For example, if your Grafana container is named grafana, set Forward Hostname / IP to grafana. This eliminates the need for port mapping on the host.

To do this, ensure your NPM container and the service container are on the same Docker network. You can add your service to the npm_network by modifying its docker-compose.yml:

services:
  grafana:
    networks:
      - npm_network

networks:
  npm_network:
    external: true

Then, restart the service.

SSL Hardening

In the SSL tab of a proxy host, you can enable HTTP/2 and Force SSL. Additionally, you can set a custom HSTS policy by adding a custom header in the Advanced tab:

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

This forces browsers to always use HTTPS.

Security Hardening for NPM Itself

  • Change the default admin port (81) to a non-standard port, e.g., 8181, in the Docker Compose file: '8181:81'. Then access the UI via http://your-server-ip:8181.
  • Use a firewall to restrict access to port 81/8181 to your local network only. For example, with UFW:
    sudo ufw allow from 192.168.1.0/24 to any port 8181 proto tcp
    sudo ufw deny 8181
    
  • Enable 2FA in NPM's settings (Settings > Two-Factor Authentication) for admin accounts.

Rate Limiting and Protection

NPM includes basic protection like Block Common Exploits. You can also add custom Nginx locations to block specific paths or set rate limits. For example, to limit requests to /api to 10 per second, add a custom location in the Advanced tab:

location /api {
    limit_req zone=mylimit burst=10 nodelay;
    proxy_pass http://your-backend:3000;
}

Using a Custom SSL Certificate

If you have a wildcard certificate from a different CA, you can upload it in NPM under Settings > SSL Certificates > Add SSL Certificate > Custom. You'll need the certificate, key, and optionally the chain. Then assign it to a proxy host in the SSL tab.

Troubleshooting Common Issues

Error Cause Solution
Error: listen EADDRINUSE: address already in use :::80 Port 80 or 443 is already used by another service (e.g., Apache). Stop the conflicting service or change NPM's port mapping in docker-compose.yml (e.g., '8080:80'), but note that SSL will not work on port 80.
502 Bad Gateway when accessing a service The forward hostname/IP or port is incorrect, or the backend service is not reachable from the NPM container. Verify the service is running and accessible from the NPM container: docker exec npm curl http://service-name:port. Check the service's network configuration.
403 Forbidden after enabling access list Your IP is not in the allowed range. Edit the access list and add your current IP (check with curl ifconfig.me).
Let's Encrypt validation fails Domain does not point to your server's public IP, or port 80 is not reachable from the internet. Ensure your DNS A record points to your server's IP. Check firewall rules allow inbound TCP/80 and TCP/443.
Database connection error on NPM startup The MariaDB container is not ready or credentials mismatch. Check the docker-compose.yml environment variables. Restart the stack with docker compose restart. If the database is corrupted, delete the ./mysql folder and recreate it (but this will lose your NPM config).
Admin UI not loading Port 81 is blocked or the container is not running. Check docker ps and access http://server-ip:81. If using a firewall, allow port 81 from your IP.

Conclusion

Nginx Proxy Manager is an essential tool for any homelab that runs multiple web services. With this guide, you've set up a secure reverse proxy with automatic SSL, access controls, and a user-friendly interface. The Docker Compose setup ensures persistence and easy updates, while the troubleshooting table helps you resolve common issues quickly.

Remember to regularly back up your configuration and monitor your SSL certificates. As you add more services, you can simply create new proxy hosts in the UI without touching any configuration files. This setup will save you hours of manual Nginx editing and keep your homelab secure and accessible.

FAQ

1. Can I use NPM with Docker containers on the same host without exposing their ports? Yes. Put both NPM and your service containers on the same Docker network (e.g., npm_network). Then use the container name as the forward hostname in NPM. This way, the service's port is not exposed to the host, reducing attack surface.

2. How do I renew Let's Encrypt certificates automatically? NPM handles renewal automatically for certificates it issued. It checks for renewal daily and renews certificates that are within 30 days of expiry. You don't need to do anything, but ensure port 80 is always reachable for the HTTP-01 challenge.

3. Is it safe to expose NPM's admin UI to the internet? No. The admin UI should only be accessible from your local network or via a VPN. Change the default port and restrict access with a firewall. If you must access it remotely, use a secure tunnel like WireGuard or Tailscale.

4. Can I use NPM to proxy non-HTTP services like SSH? NPM is designed for HTTP/HTTPS traffic. For SSH or other TCP protocols, you need a different solution like nginx stream or a dedicated proxy like frp. NPM does not support raw TCP proxying.

5. What happens if I stop the MariaDB container? If the database container stops, NPM will lose connection to its backend and may show errors or fail to start. NPM's configuration is stored in the database, so it's critical to keep the database running. Use restart: unless-stopped and monitor both containers with health checks.

⚠️ Content disclosure: This article was AI-assisted. Versions and commands can change quickly in the self-hosted world — always verify against the official documentation before running anything in production.
AdSense — In-article (responsive)

Related Guides