A Tutorial Repo for migrating your Nginx Proxy Manager proxy setup to Nginx. I wrote this originally for this reddit post and to post this my Github profile. Thought my website would also be a good place to share it for any passers-by.

To give clear instructions to help users migrate from using Nginx Proxy Manager (NPM) to standard Nginx. This tutorial is not exhaustive and there are many other implementations of this transition. I would recommend checking out the many Nginx Documentation Sites and tutorials to learn more.


If you’re anything like me and you got into the self-hosted/homelab/diy game sometime within the last 5 years, you’ve likely been recommended to use Nginx Proxy Manager as one of the choice Reverse Proxy services. If you’ve also been paying attention to various self-hosted communities, you may have also come across Christian Lempa’s Video on trusting smaller self hosted projects and tools.

Spoilers: He roasts NPM in his video and towards the end says he won’t be using NPM anymore. He also, perhaps purposely, doesn’t share which tool he will be migrating to.

Whether you follow Christian away from NPM or not, it dawned on me that while NPM is using a very trusted web server and reverse proxy under the hood, I hadn’t taken the time to understand how an Nginx Config actually worked. Since NPM was already creating most of the files for Nginx, I got to reading through all the files and reworking them so that I could begin using Nginx without the NPM gui.

Contributing: This is not all encompassing of Nginx possibilities. Including instructions for various installation methods, using OpenResty, and any other migrations or use cases would help the community. If you’d like to add in additional information on how to migrate from NPM to Nginx, that is welcome. Simply submit a PR with your steps.

TL;DR - Quick Steps

  1. Copy the following contents (including sub-directories) from the NPM /data/nginx directory to the Nginx /etc/nginx folder:

    • proxy_hosts > sites-available
    • conf.d > conf.d
    • snippets > snippets
    • custom_ssl > custom_ssl (if applicable)
  2. Edit each file in your sites-available directory and update the paths. Most will change from /data/nginx/ to /etc/nginx.

  3. Edit your nginx.conf file and ensure the following two paths are there:

    • include /etc/nginx/conf.d/*.conf; and include /etc/nginx/sites-enabled/*;
  4. Symlink the proxy host files in sites-available to sites-enabled

    • ln -s * ./sites-enabled
  5. Test your changes with nginx -t. Make appropriate changes if there are error messages.

Pre-requisites & Assumptions

I am using an Ubuntu VM with NPM and it’s db as a Docker Container while Nginx is installed natively on the machine. You don’t have to use this setup exactly, but I am making a few assumptions as to what you should have access to before you begin. I am also using custom SSL certs, but theoretically, the transition should be the same when using Lets Encrypt.

I’ve added some example files to show before and after changes to this repo and outlined file trees below.

  • You understand the basics of what a Reverse Proxy is doing and are sticking with some stock settings (like exposing port 80 an 443).
  • You’ve installed NPM and Nginx using your preferred method.
  • You have access to both NPMs file tree and Nginx’s.
  • If using NPM in docker, make sure you’ve mapped a local volume on the host to the container.
  • My setup using docker-compose is the following: /user/nginx/data:/data.
  • Know where your Nginx files are. If using docker, same as above, make sure your container directories are mapped to the host.
  • For a linux install, they should be accessible at /etc/nginx.
  • You know how to edit files at the command line using nano, vi, vim, neovim, emacs, or something else.

Nginx Files

Nginx uses the nginx.conf file and within that file, it will include your proxy files. These exist under ./nginx/sites-enabled/. In the main nginx.conf file, the line include /etc/nginx/sites-enabled/*; will bring in those files to the config file, making the proxies accessible.

How to Transition - Detailed Version

  1. Since NPM uses Nginx under the hood, they are both, by default, going to try and use ports 80 and 443 to serve up your apps and content. Turn off both systems.

    • Docker: docker stop [app_container, db_container]
    • Systemd: systemctl stop nginx
  2. Copy your proxy_host (NPM) files to the sites-available (Nginx) folder. cp -r /user/nginx/data/nginx/proxy_hosts/* /etc/nginx/sites-available/

  3. Nginx doesn’t really care what the files are called, but NPM numbers them based on the order in which you added them in the GUI. I find it better to rename them to what service they actually serve up for easier identification later.

  4. Copy your custom_ssl folder from NPM to the custom_ssl folder in Nginx. See the following step for the various default paths in both systems.

    • cp -r /user/nginx/data/custom_ssl/* /etc/nginx/custom_ssl/
  5. Copy the conf.d folder from NPM to the conf.d folder in Nginx. Note: For some reason, not all of the files in the proxy files were actually in my conf.d directory. If you’re missing any files, please download and/or copy and paste them from the NPM Repo

    • cp -r /user/nginx/data/nginx/conf.d /etc/nginx/
  6. If you had any additional files included in the Advanced section of an NPM Proxy Host, make sure you copy them over. For my setup and this tutorial, they were all located in the snippets directory.

    • cp -r /user/nginx/data/nginx/snippets/* /etc/nginx/snippets/
  7. There are a number of lines that need to be updated in each proxy configuration file to make them work with Nginx. I’ve placed additional comments in ./proxy_host/npm_proxy.conf file. The line changes are the following:

    1. Custom SSL path:

      • NPM path: /data/custom_ssl...
      • Nginx path: /etc/nginx/custom_ssl...
    2. conf.d:

      • The paths should remain the same. However, if you changed the path for conf.d in Nginx and differed in step 5, above, make sure you use the correct path.
    3. Access & Error Logs

      • NPM path: /data/logs/...
      • Nginx path: /var/log/nginx/...
  8. Double Check all your paths! If this is your first time using Nginx, make sure every directory is correct! Save your work.

  9. Navigate to the nginx.conf file which is located at /etc/nginx/. You can see the one I am using in this repo.

  10. Make sure that you have the following two lines in the main conf file and that they are pointing to the appropriate directories in the nginx directory path.

    • include /etc/nginx/conf.d/*.conf; and include /etc/nginx/sites-enabled/*;
  11. You’ll notice that you ensured there was a sites-enabled directory in the configuration file, but you changed all your proxy host config files in sites-available! Good eye, all that’s left is to symlink the files to sites-enabled so that nginx can start using them.

  12. To symlink the available proxy files run the following command within the sites-available directory:

    • ln -s * ./sites-enabled
  13. Once you’re confident that you’ve done all the above correctly, you can test your setup using nginx command and flags. While in the directory with your nginx.conf file - usually /etc/nginx - run the following command: nginx -t.

  14. If all is working as expected you should see the below output. If it returns any errors, fix them appropriately. It will usually tell you what line is throwing the error. In this case, there’s a high likelihood that it will be path error.

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

And that’s it! You can now restart your nginx service on the host and access all your sites just as if you were using Nginx Proxy Manager! Make sure you take a look at your logs and system’s status should nginx fail to start.

Additional Information/Appendix

File Trees for NPM (in container) and Nginx (on host)

I did not expand every directory in these trees. Only the ones that are pertinent for reference in this tutorial.


β”œβ”€β”€ conf.d
β”‚Β Β  └── include
β”‚Β Β      β”œβ”€β”€ assets.conf
β”‚Β Β      β”œβ”€β”€ block-exploits.conf
β”‚Β Β      β”œβ”€β”€ force-ssl.conf
β”‚Β Β      β”œβ”€β”€ ip_ranges.conf
β”‚Β Β      β”œβ”€β”€ proxy.conf
β”‚Β Β      └── resolvers.conf
β”œβ”€β”€ custom_ssl
β”‚Β Β  β”œβ”€β”€ npm-1
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ fullchain.pem
β”‚Β Β  β”‚Β Β  └── privkey.pem
β”‚Β Β  β”œβ”€β”€ npm-2
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ fullchain.pem
β”‚Β Β  β”‚Β Β  └── privkey.pem
β”‚Β Β  └── npm-3
β”‚Β Β      β”œβ”€β”€ fullchain.pem
β”‚Β Β      └── privkey.pem
β”œβ”€β”€ fastcgi.conf
β”œβ”€β”€ fastcgi_params
β”œβ”€β”€ koi-utf
β”œβ”€β”€ koi-win
β”œβ”€β”€ mime.types
β”œβ”€β”€ modules-available
β”œβ”€β”€ modules-enabled
β”œβ”€β”€ nginx.conf
β”œβ”€β”€ proxy_params
β”œβ”€β”€ scgi_params
β”œβ”€β”€ sites-available
β”‚Β Β  β”œβ”€β”€ auth.conf
β”‚Β Β  β”œβ”€β”€ bitwarden.conf
β”‚Β Β  β”œβ”€β”€ codehub.conf
β”‚Β Β  β”œβ”€β”€ default.backup
β”‚Β Β  β”œβ”€β”€ files.conf
β”‚Β Β  β”œβ”€β”€ notes.conf
β”‚Β Β  β”œβ”€β”€ photos.conf
β”‚Β Β  β”œβ”€β”€ rsmsn-root.conf
β”‚Β Β  β”œβ”€β”€ wordle.conf
β”‚Β Β  └── wordle-it.conf
β”œβ”€β”€ sites-enabled
β”‚Β Β  β”œβ”€β”€ auth.conf -> /etc/nginx/sites-available/auth.conf
β”‚Β Β  β”œβ”€β”€ bitwarden.conf -> /etc/nginx/sites-available/bitwarden.conf
β”‚Β Β  β”œβ”€β”€ codehub.conf -> /etc/nginx/sites-available/codehub.conf
β”‚Β Β  β”œβ”€β”€ files.conf -> /etc/nginx/sites-available/files.conf
β”‚Β Β  β”œβ”€β”€ notes.conf -> /etc/nginx/sites-available/notes.conf
β”‚Β Β  β”œβ”€β”€ photos.conf -> /etc/nginx/sites-available/photos.conf
β”‚Β Β  β”œβ”€β”€ wordle.conf -> /etc/nginx/sites-available/wordle.conf
β”‚Β Β  └── wordle-it.conf -> /etc/nginx/sites-available/wordle-it.conf
β”œβ”€β”€ snippets
β”‚Β Β  β”œβ”€β”€ authelia-authrequest-basic.conf
β”‚Β Β  β”œβ”€β”€ authelia-authrequest.conf
β”‚Β Β  β”œβ”€β”€ authelia-authrequest-detect.conf
β”‚Β Β  β”œβ”€β”€ authelia-location-basic.conf
β”‚Β Β  β”œβ”€β”€ authelia-location.conf
β”‚Β Β  β”œβ”€β”€ authelia-location-detect.conf
β”‚Β Β  β”œβ”€β”€ fastcgi-php.conf
β”‚Β Β  β”œβ”€β”€ proxy.conf
β”‚Β Β  └── snakeoil.conf
β”œβ”€β”€ uwsgi_params
└── win-utf


β”œβ”€β”€ data
β”‚Β Β  β”œβ”€β”€ access
β”‚Β Β  β”œβ”€β”€ custom_ssl
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ npm-1
β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ fullchain.pem
β”‚Β Β  β”‚Β Β  β”‚Β Β  └── privkey.pem
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ npm-2
β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ fullchain.pem
β”‚Β Β  β”‚Β Β  β”‚Β Β  └── privkey.pem
β”‚Β Β  β”‚Β Β  └── npm-3
β”‚Β Β  β”‚Β Β      β”œβ”€β”€ fullchain.pem
β”‚Β Β  β”‚Β Β      └── privkey.pem
β”‚Β Β  β”œβ”€β”€ keys.json
β”‚Β Β  β”œβ”€β”€ letsencrypt-acme-challenge
β”‚Β Β  β”œβ”€β”€ logs
β”‚Β Β  β”œβ”€β”€ mysql
β”‚Β Β  └── nginx
β”‚Β Β      β”œβ”€β”€ custom
β”‚Β Β      β”œβ”€β”€ dead_host
β”‚Β Β      β”œβ”€β”€ default_host
β”‚Β Β      β”œβ”€β”€ default_www
β”‚Β Β      β”œβ”€β”€ dummycert.pem
β”‚Β Β      β”œβ”€β”€ dummykey.pem
β”‚Β Β      β”œβ”€β”€ proxy_host
β”‚Β Β      β”‚Β Β  β”œβ”€β”€ 10.conf
β”‚Β Β      β”‚Β Β  β”œβ”€β”€ 11.conf
β”‚Β Β      β”‚Β Β  β”œβ”€β”€ 12.conf
β”‚Β Β      β”‚Β Β  β”œβ”€β”€ 13.conf
β”‚Β Β      β”‚Β Β  β”œβ”€β”€ 15.conf
β”‚Β Β      β”‚Β Β  β”œβ”€β”€ 1.conf
β”‚Β Β      β”‚Β Β  β”œβ”€β”€ 2.conf
β”‚Β Β      β”‚Β Β  β”œβ”€β”€ 4.conf
β”‚Β Β      β”‚Β Β  β”œβ”€β”€ 5.conf
β”‚Β Β      β”‚Β Β  └── 6.conf
β”‚Β Β      β”œβ”€β”€ redirection_host
β”‚Β Β      β”œβ”€β”€ snippets
β”‚Β Β      β”‚Β Β  β”œβ”€β”€ authelia-authrequest-basic.conf
β”‚Β Β      β”‚Β Β  β”œβ”€β”€ authelia-authrequest.conf
β”‚Β Β      β”‚Β Β  β”œβ”€β”€ authelia-authrequest-detect.conf
β”‚Β Β      β”‚Β Β  β”œβ”€β”€ authelia-location-basic.conf
β”‚Β Β      β”‚Β Β  β”œβ”€β”€ authelia-location.conf
β”‚Β Β      β”‚Β Β  β”œβ”€β”€ authelia-location-detect.conf
β”‚Β Β      β”‚Β Β  └── proxy.conf
β”‚Β Β      β”œβ”€β”€ stream
β”‚Β Β      └── temp
β”œβ”€β”€ docker-compose.yml
└── letsencrypt
└── renewal-hooks