Building Your Own Reverse Proxy Based on NPM Using Docker
This article was last updated 287 days ago. The information within may have evolved or changed; if anything is outdated or broken, please feel free to leave a message in the comments section.
Article Abstract
To address the need for deploying a reverse proxy in non-aaPanel environments such as Mac Mini, a solution is implemented by deploying Nginx Proxy Manager (NPM) via Docker containerization. This solution covers SSL certificate application, multi-scenario reverse proxy configuration, and GUI access optimization, specifically handling IP mapping issues under different bridge modes in cloud host and local network environments, including cross-host direct connection, same-host default bridging, and adaptation to the special network architecture of Docker Desktop. In practical applications, attention should be paid to the domain quota limits of Let's Encrypt certificates, and flexible forwarding of request header information is achieved through advanced parameter configuration, ultimately realizing a secure and stable reverse proxy service deployment.
Qwen3-14B · 2026-06-18

Preface

Actually, a professional reverse proxy like NPM (Nginx Proxy Manager) is more suitable. However, since the aaPanel Linux panel can support source code deployment in addition to serving as a reverse proxy, killing two birds with one stone, I have always run aaPanel Linux panel on LXC. But now I need to set up another reverse proxy on my Mac Mini, and at this time, NPM is the best choice. Therefore, this article records the process of installing and configuring NPM.

Deploy npm

Create directories on the host that need to be mounted to the container

mkdir -p /docker/npm/data
mkdir -p /docker/npm/letsencrypt

Build the npm container

The command to build npm in docker run format is as follows:

docker run --name npm  -d --restart=always \
  -p 9480:80 \ #http代理端口,请根据实际环境修改,如果部署在公网上又有合法80端口就填80
  -p 9481:81 \ #gui管理端口,请根据实际环境修改
  -p 9443:443 \ #https代理端口,请根据实际环境修改,如果部署在公网上又有合法443端口就填443
  -v /docker/npm/data:/data \ # 映射宿主机对应目录到npm容器里的data目录
  -v /docker/npm/letsencrypt:/etc/letsencrypt \ #这里是存放lets encrypt证书的地方,如果需要外部更新,比如OHTTPS之类的供应商定期来更新,就需要操作这个目录,当然,lets encrypt本身申请的证书也在这个目录里面
   jc21/nginx-proxy-manager:latest

Initialize npm

Usehttp://宿主机ip:9481Log in to npm:

image.png

Default username:[email protected], password:changeme, and you will be asked to modify the following information immediately after logging in:
image.png

After saving, you will be asked to change your password:
image.png

Then officially enter the configuration interface:
image.png

Configure reverse proxy

Apply for SSL certificate (optional)


Note: Although it is said to be optional, in reality, it is basically a must-have now, otherwise various browsers will prompt that it is insecure, which is also annoying to look at. There is a prerequisite for this step, which is that you have a public IP address, so it must be a cloud host or home broadband with a public IP, and then you have your own domain name and use a common domain name provider, such as Alibaba Cloud, Tencent Cloud, Cloudflare, etc.


Under the "SSL Certificates" menu, click "Add SSL Certificate" in the red box:

image.png

The following interface appears:
image.png

After filling it out, click "Save" at the bottom to save:
image.png

Success:
image.png

Note 1: When creating an API in Cloudflare, you must select a specific domain name to create the API, and you cannot directly use the Global API Key, otherwise an error will be reported (I can't remember exactly which version this problem appeared after, it seems to be because the Global API Key has too much permission and is insecure). The creation of a DNS specific zone API token in Cloudflare is shown in the figure below:
image.png


Note 2: Certificates applied for after Let's Encrypt uses DNSpod for DNS authentication and 1-year free certificates applied for directly on Tencent Cloud are both TrustAsia certificates, which have a limit of a maximum of 20 hostnames under the same primary domain name. This has little impact on friends with few applications, but it does affect those with many applications (like me). Therefore, if it is the same primary domain name, and you have a free certificate applied for from Tencent Cloud, and at the same time the certificates you applied for using Let's Encrypt yourself all share this 20-quota limit, you must pay attention.

Create a reverse proxy for the application

Assuming that on Cloudflare I have pointed the A record of abc.tangwudi.xyz to the public IP of the cloud host, and the application on the cloud host that needs to be reverse proxied is the application on port 8080, and the HTTPS service is enabled, then configure the reverse proxy according to the steps below.
Under the "Hosts" menu item in the red box, click "Add Proxy Host" in the red box below:

image.png

Configure as follows in the "Details" interface of "New Proxy Host":
image.png


Regarding how to fill in "Forward Hostname IP" and "Forward Port", you must first clarify the positional relationship between npm and the application that needs to be reverse proxied in your actual environment. Why say so? Because "Forward Hostname IP" tells npm what address to use to access your deployed application, and your deployed application may be on a different host from npm, or on the same bridge of the same host, or on different bridges of the same host. This leads to multiple reachabilities between npm and the application. Below we will discuss two scenarios.
npm and the application are located on different hosts
This method is the simplest. Because in this deployment method, the application must have port mapping to be published externally (if deployed via Docker, the -p parameter is used, and the port mapping is the port before the colon :). Therefore, "Forward Hostname IP" can be directly filled with the IP of the other host, and "Forward Port" can be directly filled with the published port.
npm and the application are located on the same host
This method is complicated. I estimate that some friends are often confused by this method. At this time, it is generally divided into 3 situations:
1. Both npm and the application are connected to the same non-default bridge
For example, if the docker run command is used to deploy both npm and the application with -net=public-net, then at this time you can directly use the application's container name instead of the IP in "Forward Hostname IP", and at the same time, "Forward Port" can be directly filled with the real port of the application container.
2. Both npm and the application are in the default bridge (non-Docker Desktop environment)
By default, containers that do not use the -net parameter are assigned to the default bridge. The IP of the network card (docker0) connecting the default bridge to the host is generally 172.17.0.1 (which can be confirmed with the ip addr show docker0 command on Debian). Therefore, at this time, "Forward Hostname IP" can be directly filled with 172.17.0.1, and "Forward Port" can be directly filled with the application's externally mapped port (i.e., the port before the colon in the -p parameter).

In fact, as long as the application is in the default bridge, this method can be used. Even if npm is in another non-default bridge, it can also communicate with the application using 172.17.0.1 and the application's externally mapped port. And this method can actually be expanded. If there are other bridges, their gateway IPs such as 172.18.0.1, 172.19.0.1, etc., can have similar usage.
3. Both npm and the application are connected to the default bridge (Docker Desktop environment)
"Forward Hostname IP" can be directly filled with the host IP, and "Forward Port" can be directly filled with the real port mapped on the host (i.e., the port before the colon in the -p parameter).


Why distinguish between "Docker Desktop environment" and "non-Docker Desktop environment"? Because the virtual network card situation in the "Docker Desktop environment" is different from that in the "non-Docker Desktop environment". For example, my Mac Mini is a Docker environment deployed through Docker Desktop, which does not have the docker0 network card:

image.png

Therefore, pinging the address "172.17.0.1" also does not work:
image.png

On the other hand, another docker environment deployed directly on Debian does have the docker0 network card:
image.png

And it can also ping 172.17.0.1:
image.png

As for the commonly used 127.0.0.1, this is typically used for reverse proxying at the operating system level, such as using the Baota Linux panel for reverse proxying, or using Nginx deployed directly from source code for reverse proxying, etc. It is generally not applicable to the usual container deployment method of npm.


Then configure HTTPS, make the settings in the SSL menu as shown in the figure below, and then click "save" to save:

image.png

Success:
image.png

Note: In the "Advanced" menu of the "New Proxy Host" interface, you can fill in advanced parameters, such as the domain name, port, etc., passed to the backend application. In some special cases, when the reverse proxy needs to pass a domain name or port different from the host field in the access request, this advanced parameter needs to be used. For example, if I want to accesshttps://abc.tangwudi.xyzrequests, and after reaching the reverse proxy, the host passed by the reverse proxy to the backend application is actually def.tangwudi.xyz, then the configuration is as follows:

image.png

The code is as follows:

location  / remote_addr;proxy_add_x_forwarded_for; remote_addr;http_upgrade;

Note: The configuration in "Advanced" will override the settings in the "Details" menu.

Reverse proxy GUI access

Previously, we used thehttp://宿主机ip:9481method to access the NPM GUI. While this method works fine on the intranet, it is definitely not feasible to access it from the public internet as it is too insecure. In this case, you can use NPM itself to reverse proxy the access to the GUI page, as follows:

image.png

image.png

After that, you can usehttps://npmgui.tangwudi.xyz:9443to access the GUI (provided that port 9443 is also mapped to port 9443 of 192.168.10.133 on the router).

View Related Categories · 3 Matches
📌 Content Structure Prompt:
This content is part of the following knowledge map, where you can view the complete content path: Blog Knowledge Map。
📎 Related Articles
Share this article
The blog content is original, please indicate the source when reposting! The RSS address of the blog is:https://blog.tangwudi.com/feed, welcome to subscribe; if needed, you can join theTelegram Groupto discuss questions together.
No Comments

Send Comment Edit Comment


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗ
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
Kaomoji
Emoji
Little Dinosaur
Flower!
Previous
Next
       

👋 Welcome to “Wudi's Personal Blog”

Here, long-term exploration is mainly carried out around the following directions:

🧱 Personal Digital Infrastructure and Blog System Construction
☁️ Cloudflare and Network Architecture Practice
🧠 AI and Knowledge System Exploration
🛡️ Network Security and Access Optimization
🎵 Music and Sound Cognition
👁️ Cognitive Perspectives and Worldviews