knktc's Notes

python, cloud, linux...

0%

How I Have Used Nginx in Real Projects

Over the past few years, Nginx has shown up in project after project for me. In many cases it has been more than just a request entry point. It often ends up being the quickest and most effective layer to work with when something needs to be connected across frontend, backend, and network boundaries.

These days, more systems are moving toward cloud-native components such as Envoy Proxy for some of the jobs that Nginx used to handle. Even so, when I look back at actual delivery work, a lot of problems were first made workable with Nginx.

So I wanted to write down a few of the ways I have used it in real projects. They are not always the most elegant or the most standard solutions, but they all solved real problems in real environments.

1. Proxying SMTP so internal services can still send email

This is a fairly common situation in customer environments.

Sometimes a system is deployed inside a private network, and the business service itself has no direct access to the internet, but it still needs to send email. In the long run, the proper fix is usually to allow the relevant nodes to reach the SMTP server directly. But if you need something practical in the short term, Nginx can also be used as a TCP proxy.

The configuration can look like this:

1
2
3
4
5
6
7
8
load_module /usr/lib/nginx/modules/ngx_stream_module.so;

stream {
server {
listen 1587;
proxy_pass mail.example.com:587;
}
}

With this in place, the internal service no longer needs to connect to the external SMTP server directly. It can simply use the Nginx node IP + port 1587 as its SMTP endpoint.

The key point here is that this is not an HTTP reverse proxy. It relies on the stream module for layer-4 forwarding.

If you are using the Ubuntu Nginx package installed via apt, ngx_stream_module is usually already available. If you compile Nginx yourself, make sure it was built with --with-stream.

2. Proxying enterprise messaging webhooks such as WeCom, DingTalk, or Feishu

This is really the same class of problem.

In some internal deployments, services do not just need to send email. They also need to send notifications to tools like WeCom, DingTalk, or Feishu. If the service itself cannot reach the public network, the requests can be routed through an Nginx node that can.

Take a WeCom bot webhook as an example. It usually looks like this:

1
https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=3e8b437b-419f-46b2-9495-391cf17a6719

If the internal service cannot access the internet directly, you can add a reverse proxy on Nginx like this:

1
2
3
4
5
6
7
8
server {
listen 80;
server_name wechat-proxy;

location /cgi-bin/webhook {
proxy_pass https://qyapi.weixin.qq.com;
}
}

After that, the internal service can replace the original public webhook URL with something like this internal one:

1
https://wechat-proxy/cgi-bin/webhook/send?key=3e8b437b-419f-46b2-9495-391cf17a6719

At its core, this is still the same pattern: let a service that cannot reach the internet borrow a node that can.

The nice part is that it is quick to implement and usually requires only small changes on the application side. The downside is that it is more of an engineering compromise than a long-term design. If you keep accumulating this kind of dependency, it is usually a sign that outbound access and proxy strategy should be designed more systematically.

3. Using auth_request to add authentication in front of static resources and internal services

Another situation I have run into quite a few times is this: some resources are technically simple, such as API docs, template files, installers, or even a small frontend page served directly by Nginx. During development, people tend to think, “it is just a static file,” and expose it directly.

But in customer environments, these paths often get flagged by security scans. Even when the file itself is not sensitive, “publicly accessible without authentication” is often enough to trigger concerns.

That is where auth_request becomes very useful. It allows Nginx to call an authentication endpoint before actually returning the resource.

For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
location /docs {
auth_request /auth;
proxy_pass http://frontend/docs;
proxy_set_header Host $host;
error_page 401 = @error401;
}

location = /auth {
proxy_pass http://backend/auth;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}

location @error401 {
return 302 /login;
}

The nice thing about this approach is that the backend only needs to provide a dedicated authentication-check endpoint, and Nginx can then reuse it in front of multiple entry points.

This is useful not only for static resources, but also for internal services that you do not want to burden with their own authentication implementation. The service focuses on its actual job, and Nginx handles access control at the edge.

The authentication service can also return context data that Nginx passes along to downstream services. For example, if the auth endpoint returns the username:

1
2
auth_request_set $username $upstream_http_x_username;
proxy_set_header X-Username $username;

Then the backend service can simply read X-Username without having to re-parse the session state itself.

Over the years I have worked on quite a few single sign-on integrations in customer environments. What stood out to me was not that there are many standard SSO solutions, but that real-world deployments often do not stay standard for very long.

Once integration work starts, the authentication flow often gets mixed with customer-specific requirements. In theory you can talk about OAuth2, OIDC, or SAML. In practice, you often end up adapting to whatever the customer’s existing process already expects.

In that kind of environment, one practical approach we sometimes used was this: let the backend create a session for a specific user first, and then let Nginx return a Set-Cookie header so the browser gets a valid session cookie under the current domain. From the user’s perspective, it becomes a “click once and you are in” flow.

Assume the system stores login state in a session_key cookie. Then the Nginx side can look like this:

1
2
3
4
location ~ ^/set-session/(?<session_key>\w+)$ {
add_header Set-Cookie "session_key=$session_key; HttpOnly; Max-Age=21600; Path=/; SameSite=Lax";
return 302 /;
}

The supporting flow is also straightforward:

  1. The backend provides an administrative API that creates a session for a specified user.
  2. That API returns the generated session_key.
  3. The caller builds a URL like /set-session/<session_key>.
  4. The user’s browser opens that URL, Nginx writes the cookie, and the browser is redirected to the homepage.

For example, if the system URL is:

1
http://example.com

and the generated session_key is:

1
d79769307e7e443ca7abfd780e89217e

then the final URL becomes:

1
http://example.com/set-session/d79769307e7e443ca7abfd780e89217e

Once the browser visits that URL, the corresponding session_key is written under the current domain and the user lands back on the homepage already logged in.

This is obviously not a universal SSO solution, and it is not suitable for every system. But in heavily customized project environments where integration requirements are very specific and timelines are tight, it can be surprisingly effective. Its value is not elegance. Its value is that it is simple, localized, and controllable.

Final thoughts

Looking back at these examples, what makes Nginx genuinely useful in projects is often not just that it can forward requests, but that it can quickly add a missing capability at a system boundary.

Sometimes that missing piece is outbound network access. Sometimes it is authentication. Sometimes it is a non-standard integration step that still has to be delivered.

That is still my basic view of Nginx today: it may not be the final architecture for every problem, but in a lot of real projects, it is the tool that gets things working first.

如果我的文字帮到了您,那么可不可以请我喝罐可乐?

Welcome to my other publishing channels