nginx-proxy-manager Documentation

Docker container for managing Nginx proxy hosts with a simple, powerful interface

33,849 stars TypeScript TypeScript 1 file Β· ~6,810 tokens #nginx#nginx-proxy
RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy nginx-proxy-manager on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name nginx-proxy-manager --restart always nginxproxymanager/nginx-proxy-manager:latest
Configured Services & Ports
βš™οΈ cypress
image: build locally
βš™οΈ fullstack
image: build locally
docker-compose.yml
yaml (docker-compose.yml)
# WARNING: This is a CI docker-compose file used for building and testing of the entire app, it should not be used for production.
services:
  cypress:
    environment:
      CYPRESS_stack: "sqlite"
    command: cypress run --browser chrome --config-file=cypress/config/ci.mjs --expose grepTags="@isolated"

  fullstack:
    environment:
      DB_SQLITE_FILE: "/data/mydb.sqlite"
      PUID: 1000
      PGID: 1000
      DISABLE_IPV6: "true"

File: README.md

This project comes as a pre-built Docker image that enables you to easily forward to your websites
running at home or otherwise, including free SSL, without having to know too much about Nginx or Letsencrypt.

Project Goal

I created this project to fill a personal need to provide users with an easy way to accomplish reverse
proxying hosts with SSL termination, and it had to be so easy that a monkey could do it. This goal hasn't changed.
While there might be advanced options, they are optional, and the project should be as simple as possible
so that the barrier to entry here is low.

Features

  • Beautiful and Secure Admin Interface based on Tabler
  • Easily create forwarding domains, redirections, streams, and 404 hosts without knowing anything about Nginx
  • Free SSL using Let's Encrypt or provide your own custom SSL certificates
  • Access Lists and basic HTTP Authentication for your hosts
  • Advanced Nginx configuration available for super users
  • User management, permissions, and audit log

::: warning
armv7 is no longer supported in version 2.14+. This is due to Nodejs dropping support for armhf. Please
use the 2.13.7 image tag if this applies to you.
:::

Hosting your home network

I won't go into too much detail here, but here are the basics for someone new to this self-hosted world.

  1. Your home router will have a Port Forwarding section somewhere. Log in and find it
  2. Add port forwarding for ports 80 and 443 to the server hosting this project
  3. Configure your domain name details to point to your home, either with a static ip or a service like
  4. Use the Nginx Proxy Manager as your gateway to forward to your other web-based services

Quick Setup

  1. Install Docker
  2. Create a docker-compose.yml file similar to this:
yml
services:
  app:
    image: 'docker.io/jc21/nginx-proxy-manager:latest'
    restart: unless-stopped
    ports:
      - '80:80'
      - '81:81'
      - '443:443'
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt

This is the bare minimum configuration required. See the documentation for more.

  1. Bring up your stack by running
bash
docker compose up -d
  1. Log in to the Admin UI

When your docker container is running, connect to it on port 81 for the admin interface.
Sometimes this can take a little bit because of the entropy of keys.

http://127.0.0.1:81

Contributing

All are welcome to create pull requests for this project, against the develop branch. Official releases are created from the master branch.

CI is used in this project. All PR's must pass before being considered. After passing,
docker builds for PR's are available on dockerhub for manual verifications.

Documentation within the develop branch is available for preview at
https://develop.nginxproxymanager.com

Contributors

Special thanks to all of our contributors.

Getting Support

  1. Found a bug?
  2. Discussions
  3. Reddit

File: backend/certbot/README.md

Certbot dns-plugins

This file contains info about available Certbot DNS plugins.
This only works for plugins which use the standard argument structure, so:
--authenticator ---credentials ---propagation-seconds

File Structure:

json
{
  "cloudflare": {
    "display_name": "Name displayed to the user",
    "package_name": "Package name in PyPi repo",
    "version_requirement": "Optional package version requirements (e.g. ==1.3 or >=1.2,<2.0, see https://www.python.org/dev/peps/pep-0440/#version-specifiers)",
    "dependencies": "Additional dependencies, space separated (as you would pass it to pip install)",
    "credentials": "Template of the credentials file",
    "full_plugin_name": "The full plugin name as used in the commandline with certbot, e.g. 'dns-njalla'"
  },
  ...
}

File: backend/config/README.md

These files are use in development and are not deployed as part of the final product.


File: docs/src/guide/index.md


outline: deep

Guide

::: raw

:::

This project comes as a pre-built docker image that enables you to easily forward to your websites
running at home or otherwise, including free SSL, without having to know too much about Nginx or Letsencrypt.

Project Goal

I created this project to fill a personal need to provide users with an easy way to accomplish reverse
proxying hosts with SSL termination and it had to be so easy that a monkey could do it. This goal hasn't changed.
While there might be advanced options they are optional and the project should be as simple as possible
so that the barrier for entry here is low.

::: raw

:::

Features

  • Beautiful and Secure Admin Interface based on Tabler
  • Easily create forwarding domains, redirections, streams and 404 hosts without knowing anything about Nginx
  • Free SSL using Let's Encrypt or provide your own custom SSL certificates
  • Access Lists and basic HTTP Authentication for your hosts
  • Advanced Nginx configuration available for super users
  • User management, permissions and audit log

Hosting your home network

I won't go in to too much detail here but here are the basics for someone new to this self-hosted world.

  1. Your home router will have a Port Forwarding section somewhere. Log in and find it
  2. Add port forwarding for port 80 and 443 to the server hosting this project
  3. Configure your domain name details to point to your home, either with a static ip or a service like DuckDNS or Amazon Route53
  4. Use the Nginx Proxy Manager as your gateway to forward to your other web based services

Quick Setup

  1. Install Docker and Docker-Compose
  1. Create a docker-compose.yml file similar to this:
yml
services:
  app:
    image: 'jc21/nginx-proxy-manager:{{VERSION}}'
    restart: unless-stopped
    environment:
      TZ: "Australia/Brisbane"
    ports:
      - '80:80'
      - '81:81'
      - '443:443'
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt

This is the bare minimum configuration required. See the documentation for more.

  1. Bring up your stack by running
bash
docker compose up -d
  1. Log in to the Admin UI

When your docker container is running, connect to it on port 81 for the admin interface.

http://127.0.0.1:81

This startup can take a minute depending on your hardware.

Contributing

All are welcome to create pull requests for this project, against the develop branch. Official releases are created from the master branch.

CI is used in this project. All PR's must pass before being considered. After passing,
docker builds for PR's are available on dockerhub for manual verifications.

Documentation within the develop branch is available for preview at
https://develop.nginxproxymanager.com

Contributors

Special thanks to all of our contributors.

Getting Support

  1. Found a bug?
  2. Discussions
  3. Reddit

File: docs/src/faq/index.md


outline: deep

FAQ

Do I have to use Docker?

Yes, that's how this project is packaged.

This makes it easier to support the project when we have control over the version of Nginx other packages
use by the project.

Can I run it on a Raspberry Pi?

Yes! The docker image is multi-arch and is built for a variety of architectures. If yours is
not listed please open a
GitHub issue.

I can't get my service to proxy properly?

Your best bet is to ask the Reddit community for support. There's safety in numbers.

When adding username and password access control to a proxy host, I can no longer login into the app.

Having an Access Control List (ACL) with username and password requires the browser to always send this username
and password in the Authorization header on each request. If your proxied app also requires authentication (like
Nginx Proxy Manager itself), most likely the app will also use the Authorization header to transmit this information,
as this is the standardized header meant for this kind of information. However having multiples of the same headers
is not allowed in the internet standard and almost all apps
do not support multiple values in the Authorization header. Hence one of the two logins will be broken. This can
only be fixed by either removing one of the logins or by changing the app to use other non-standard headers for authorization.


File: docs/src/advanced-config/index.md


outline: deep

Advanced Configuration

Running processes as a user/group

By default, the services (nginx etc) will run as root user inside the docker container.
You can change this behaviour by setting the following environment variables.
Not only will they run the services as this user/group, they will change the ownership
on the data and letsencrypt folders at startup.

yml
services:
  app:
    image: 'jc21/nginx-proxy-manager:{{VERSION}}'
    environment:
      PUID: 1000
      PGID: 1000
    # ...

This may have the side effect of a failed container start due to permission denied trying
to open port 80 on some systems. The only course to fix that is to remove the variables
and run as the default root user.

Best Practice: Use a Docker network

For those who have a few of their upstream services running in Docker on the same Docker
host as NPM, here's a trick to secure things a bit better. By creating a custom Docker network,
you don't need to publish ports for your upstream services to all of the Docker host's interfaces.

Create a network, ie "scoobydoo":

bash
docker network create scoobydoo

Then add the following to the docker-compose.yml file for both NPM and any other
services running on this Docker host:

yml
networks:
  default:
    external: true
    name: scoobydoo

Let's look at a Portainer example:

yml
services:

  portainer:
    image: portainer/portainer
    privileged: true
    volumes:
      - './data:/data'
      - '/var/run/docker.sock:/var/run/docker.sock'
    restart: unless-stopped

networks:
  default:
    external: true
    name: scoobydoo

Now in the NPM UI you can create a proxy host with portainer as the hostname,
and port 9000 as the port. Even though this port isn't listed in the docker-compose
file, it's "exposed" by the Portainer Docker image for you and not available on
the Docker host outside of this Docker network. The service name is used as the
hostname, so make sure your service names are unique when using the same network.

Docker Healthcheck

The Dockerfile that builds this project does not include a HEALTHCHECK but you can opt in to this
feature by adding the following to the service in your docker-compose.yml file:

yml
healthcheck:
  test: ["CMD", "/usr/bin/check-health"]
  interval: 10s
  timeout: 3s

Docker File Secrets

This image supports the use of Docker secrets to import from files and keep sensitive usernames or passwords from being passed or preserved in plaintext.

You can set any environment variable from a file by appending __FILE (double-underscore FILE) to the environmental variable name.

yml
secrets:
  # Secrets are single-line text files where the sole content is the secret
  # Paths in this example assume that secrets are kept in local folder called ".secrets"
  DB_ROOT_PWD:
    file: .secrets/db_root_pwd.txt
  MYSQL_PWD:
    file: .secrets/mysql_pwd.txt

services:
  app:
    image: 'jc21/nginx-proxy-manager:{{VERSION}}'
    restart: unless-stopped
    ports:
      # Public HTTP Port:
      - '80:80'
      # Public HTTPS Port:
      - '443:443'
      # Admin Web Port:
      - '81:81'
    environment:
      # These are the settings to access your db
      DB_MYSQL_HOST: "db"
      DB_MYSQL_PORT: 3306
      DB_MYSQL_USER: "npm"
      # DB_MYSQL_PASSWORD: "npm"  # use secret instead
      DB_MYSQL_PASSWORD__FILE: /run/secrets/MYSQL_PWD
      DB_MYSQL_NAME: "npm"
      # If you would rather use Sqlite, remove all DB_MYSQL_* lines above
      # Uncomment this if IPv6 is not enabled on your host
      # DISABLE_IPV6: 'true'
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
    secrets:
      - MYSQL_PWD
    depends_on:
      - db

  db:
    image: 'linuxserver/mariadb'
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD__FILE: /run/secrets/DB_ROOT_PWD
      MYSQL_DATABASE: 'npm'
      MYSQL_USER: 'npm'
      MYSQL_PASSWORD__FILE: /run/secrets/MYSQL_PWD
      TZ: 'Australia/Brisbane'
    volumes:
      - ./mariadb:/config
    secrets:
      - DB_ROOT_PWD
      - MYSQL_PWD

Disabling IPv6

On some Docker hosts IPv6 may not be enabled. In these cases, the following message may be seen in the log:

Address family not supported by protocol

The easy fix is to add a Docker environment variable to the Nginx Proxy Manager stack:

yml
environment:
      DISABLE_IPV6: 'true'

Disabling IP Ranges Fetch

By default, NPM fetches IP ranges from CloudFront and Cloudflare during application startup. In environments with limited internet access or to speed up container startup, this fetch can be disabled:

yml
environment:
      IP_RANGES_FETCH_ENABLED: 'false'

Custom Nginx Configurations

If you are a more advanced user, you might be itching for extra Nginx customizability.

NPM has the ability to include different custom configuration snippets in different places.

You can add your custom configuration snippet files at /data/nginx/custom as follow:

  • /data/nginx/custom/root_top.conf: Included at the top of nginx.conf
  • /data/nginx/custom/root.conf: Included at the very end of nginx.conf
  • /data/nginx/custom/http_top.conf: Included at the top of the main http block
  • /data/nginx/custom/http.conf: Included at the end of the main http block
  • /data/nginx/custom/events.conf: Included at the end of the events block
  • /data/nginx/custom/stream.conf: Included at the end of the main stream block
  • /data/nginx/custom/server_proxy.conf: Included at the end of every proxy server block
  • /data/nginx/custom/server_redirect.conf: Included at the end of every redirection server block
  • /data/nginx/custom/server_stream.conf: Included at the end of every stream server block
  • /data/nginx/custom/server_stream_tcp.conf: Included at the end of every TCP stream server block
  • /data/nginx/custom/server_stream_udp.conf: Included at the end of every UDP stream server block
  • /data/nginx/custom/server_dead.conf: Included at the end of every 404 server block

Every file is optional.

X-FRAME-OPTIONS Header

You can configure the X-FRAME-OPTIONS header
value by specifying it as a Docker environment variable. The default if not specified is deny.

yml
...
  environment:
    X_FRAME_OPTIONS: "sameorigin"
  ...

Customising logrotate settings

By default, NPM rotates the access- and error logs weekly and keeps 4 and 10 log files respectively.
Depending on the usage, this can lead to large log files, especially access logs.
You can customise the logrotate configuration through a mount (if your custom config is logrotate.custom):

yml
volumes:
    ...
    - ./logrotate.custom:/etc/logrotate.d/nginx-proxy-manager

For reference, the default configuration can be found here.

Enabling the geoip2 module

To enable the geoip2 module, you can create the custom configuration file /data/nginx/custom/root_top.conf and include the following snippet:

text
load_module /usr/lib/nginx/modules/ngx_http_geoip2_module.so;
load_module /usr/lib/nginx/modules/ngx_stream_geoip2_module.so;

Auto Initial User Creation

Setting these environment variables will create the default user on startup, skipping the UI first user setup screen:

yml
environment:
      INITIAL_ADMIN_EMAIL: [email protected]
      INITIAL_ADMIN_PASSWORD: mypassword1

Disable Nginx Resolver

On startup, we generate a resolvers directive for Nginx unless this is defined:

yml
environment:
      DISABLE_RESOLVER: true

In this configuration, all DNS queries performed by Nginx will fall to the /etc/hosts file
and then the /etc/resolv.conf.

Changing the Admin UI port from 81 to something else

First, add an env var to your docker compose file:

yml
environment:
      NPM_ADMIN_PORT: '8000'

And you'll probably want to expose that port as well

yml
ports:
      - '8000:8000'

Then you'll be able to access admin UI at http://localhost:8000


File: docs/src/screenshots/index.md


outline: deep

Screenshots

Light Mode

::: raw

text
[](/screenshots/light/01_first-user.png)
[](/screenshots/light/02_login.png)
[](/screenshots/light/03_dashboard.png)
[](/screenshots/light/04_proxy-hosts.png)
[](/screenshots/light/05_redirection_hosts.png)
[](/screenshots/light/06_streams.png)
[](/screenshots/light/07_404_hosts.png)
[](/screenshots/light/08_access-lists.png)
[](/screenshots/light/09_certificates.png)
[](/screenshots/light/10_users.png)
[](/screenshots/light/11_audit-logs.png)
[](/screenshots/light/12_settings.png)
[](/screenshots/light/13_add-proxy_host.png)
[](/screenshots/light/14_add_proxy_host_dns.png)

:::

Dark Mode

::: raw

text
[](/screenshots/dark/01_first-user.png)
[](/screenshots/dark/02_login.png)
[](/screenshots/dark/03_dashboard.png)
[](/screenshots/dark/04_proxy-hosts.png)
[](/screenshots/dark/05_redirection_hosts.png)
[](/screenshots/dark/06_streams.png)
[](/screenshots/dark/07_404_hosts.png)
[](/screenshots/dark/08_access-lists.png)
[](/screenshots/dark/09_certificates.png)
[](/screenshots/dark/10_users.png)
[](/screenshots/dark/11_audit-logs.png)
[](/screenshots/dark/12_settings.png)
[](/screenshots/dark/13_add-proxy_host.png)
[](/screenshots/dark/14_add_proxy_host_dns.png)

:::


File: docs/src/setup/index.md


outline: deep

Full Setup Instructions

Running the App

Create a docker-compose.yml file:

yml
services:
  app:
    image: 'jc21/nginx-proxy-manager:{{VERSION}}'
    restart: unless-stopped

    ports:
      # These ports are in format <host-port>:<container-port>
      - '80:80' # Public HTTP Port
      - '443:443' # Public HTTPS Port
      - '81:81' # Admin Web Port
      # Add any other Stream port you want to expose
      # - '21:21' # FTP

    environment:
      TZ: "Australia/Brisbane"

      # Uncomment this if you want to change the location of
      # the SQLite DB file within the container
      # DB_SQLITE_FILE: "/data/database.sqlite"

      # Uncomment this if IPv6 is not enabled on your host
      # DISABLE_IPV6: 'true'

    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt

Then:

bash
docker compose up -d

Using MySQL / MariaDB Database

If you opt for the MySQL configuration you will have to provide the database server yourself.

It's easy to use another docker container for your database also and link it as part of the docker stack, so that's what the following examples
are going to use.

Here is an example of what your docker-compose.yml will look like when using a MariaDB container:

yml
services:
  app:
    image: 'jc21/nginx-proxy-manager:{{VERSION}}'
    restart: unless-stopped
    ports:
      # These ports are in format <host-port>:<container-port>
      - '80:80' # Public HTTP Port
      - '443:443' # Public HTTPS Port
      - '81:81' # Admin Web Port
      # Add any other Stream port you want to expose
      # - '21:21' # FTP
    environment:
      TZ: "Australia/Brisbane"
      # Mysql/Maria connection parameters:
      DB_MYSQL_HOST: "db"
      DB_MYSQL_PORT: 3306
      DB_MYSQL_USER: "npm"
      DB_MYSQL_PASSWORD: "npm"
      DB_MYSQL_NAME: "npm"
      # Optional SSL (see section below)
      # DB_MYSQL_SSL: 'true'
      # DB_MYSQL_SSL_REJECT_UNAUTHORIZED: 'true'
      # DB_MYSQL_SSL_VERIFY_IDENTITY: 'true'
      # Uncomment this if IPv6 is not enabled on your host
      # DISABLE_IPV6: 'true'
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
    depends_on:
      - db

  db:
    image: 'linuxserver/mariadb'
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: 'npm'
      MYSQL_DATABASE: 'npm'
      MYSQL_USER: 'npm'
      MYSQL_PASSWORD: 'npm'
      TZ: 'Australia/Brisbane'
    volumes:
      - ./mariadb:/config

::: warning
Please note, that DB_MYSQL_* environment variables will take precedent over DB_SQLITE_* variables. So if you keep the MySQL variables, you will not be able to use SQLite.
:::

Optional: MySQL / MariaDB SSL

You can enable TLS for the MySQL/MariaDB connection with these environment variables:

  • DB_MYSQL_SSL: Enable SSL when set to true. If unset or false, SSL disabled (previous default behaviour).
  • DB_MYSQL_SSL_REJECT_UNAUTHORIZED: (default: true) Validate the server certificate chain. Set to false to allow self‑signed/unknown CA.
  • DB_MYSQL_SSL_VERIFY_IDENTITY: (default: true) Performs host name / identity verification.

Enabling SSL using a self-signed cert (not recommended for production).

Using Postgres database

Similar to the MySQL server setup:

yml
services:
  app:
    image: 'jc21/nginx-proxy-manager:{{VERSION}}'
    restart: unless-stopped
    ports:
      # These ports are in format <host-port>:<container-port>
      - '80:80' # Public HTTP Port
      - '443:443' # Public HTTPS Port
      - '81:81' # Admin Web Port
      # Add any other Stream port you want to expose
      # - '21:21' # FTP
    environment:
      TZ: "Australia/Brisbane"
      # Postgres parameters:
      DB_POSTGRES_HOST: 'db'
      DB_POSTGRES_PORT: '5432'
      DB_POSTGRES_USER: 'npm'
      DB_POSTGRES_PASSWORD: 'npmpass'
      DB_POSTGRES_NAME: 'npm'
      # Uncomment this if IPv6 is not enabled on your host
      # DISABLE_IPV6: 'true'
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
    depends_on:
      - db

  db:
    image: postgres:17
    environment:
      POSTGRES_USER: 'npm'
      POSTGRES_PASSWORD: 'npmpass'
      POSTGRES_DB: 'npm'
    volumes:
      - ./postgres_data:/var/lib/postgresql/data

::: warning

Custom Postgres schema is not supported, as such public will be used.

:::

Running on Raspberry PI / ARM devices

The docker images support the following architectures:

  • amd64
  • arm64

::: warning
armv7 is no longer supported in version 2.14+. This is due to Nodejs dropping support for armhf. Please
use the 2.13.7 image tag if this applies to you.
:::

The docker images are a manifest of all the architecture docker builds supported, so this means
you don't have to worry about doing anything special and you can follow the common instructions above.

Check out the dockerhub tags
for a list of supported architectures and if you want one that doesn't exist,
create a feature request.

Also, if you don't know how to already, follow this guide to install docker and docker-compose
on Raspbian.

Initial Run

After the app is running for the first time, the following will happen:

  1. JWT keys will be generated and saved in the data folder
  2. The database will initialize with table structures
  3. A default admin user will be created

This process can take a couple of minutes depending on your machine.


File: docs/src/third-party/index.md


outline: deep

Third Party

As this software gains popularity it's common to see it integrated with other platforms. Please be aware that unless specifically mentioned in the documentation of those
integrations, they are not supported by me.

Known integrations:

If you would like your integration of NPM listed, please open a
Github issue


File: docs/src/upgrading/index.md


outline: deep

Upgrading

bash
docker compose pull
docker compose up -d

This project will automatically update any databases or other requirements so you don't have to follow
any crazy instructions. These steps above will pull the latest updates and recreate the docker
containers.

See the list of releases for any upgrade steps specific to each release.