Skip to main content
In this lesson you’ll configure an NGINX server to:
  • Add common HTTP security headers so browsers handle your site more safely.
  • Extend NGINX to act as a TLS-terminating load balancer that forwards requests to two Apache backends.
  • Preserve original client metadata for backend logging by sending proxy headers (client IP, forwarded chain, and original protocol).
We’ll first inspect the site with no custom headers, then add security headers and verify them. Finally we’ll add an upstream and proxy headers so the backend Apache servers can log the original client information. Initial local DNS record (example)
A quick initial test (no custom headers yet). Note the 200 OK and the default Server header from NGINX:
Open your browser DevTools → Network and inspect a resource to view the response headers (Date, Server, Content-Type, etc.). We will add security headers that improve how browsers treat your site and confirm the headers in both curl and browser responses. We’re also going to configure NGINX as a load balancer and forward proxy headers so backend Apache servers receive the original client information. Without these proxy headers, backend logs will show the load balancer IP as the client for every request.
A simple architecture diagram showing a client browser sending requests to an NGINX load balancer, which forwards traffic to two backend Apache web servers (labeled 1 and 2). Icons for monitoring/health checks are also shown.
These proxy headers allow the backend web servers to see where the request originated from and whether it arrived via HTTPS.

Add security headers to the NGINX site config

  • Edit your site config (for example /etc/nginx/sites-available/example-https).
  • Place the headers inside the server { listen 443 ssl; ... } block to apply them globally for that TLS server (you may also set them per location if needed).
Example NGINX server configuration with common security headers:
Table — security headers included and their purpose:
By default add_header applies only to certain response codes (200, 204, 301, 302, 303, 304, 307, 308). To include headers on error responses as well, append the always parameter, for example: add_header Strict-Transport-Security "..." always;. Remember to include semicolons at the end of each directive.
Validate and reload NGINX:
Verify headers are present:
You should now also see these headers in the browser DevTools → Network → Response headers.

Add upstream and proxy headers for backend logging

To make backend Apache servers see the original client IP, forwarded chain, and protocol, create an upstream block and configure proxy_set_header in the location that proxies requests. Example NGINX config with upstream and proxy headers:
If you proxy to upstreams over HTTPS (for example node01:443), enable SNI and configure TLS verification appropriately. Use proxy_ssl_server_name on; and set proxy_ssl_trusted_certificate with proxy_ssl_verify on; for production. Disabling verification (proxy_ssl_verify off;) may be acceptable in isolated labs but is unsafe for production.
Note: when using proxy_pass to a named upstream that uses HTTPS, NGINX will not automatically set the backend server name for SNI unless proxy_ssl_server_name on; is present and the upstream server name is resolvable. Test and reload NGINX after changes:

Inspect backend Apache logs

Default Apache logs are usually under /var/log/apache2/. Tail the access.log to watch requests as the load balancer forwards them. Example of following the Apache access log before proxy headers were added:
To include the proxy headers in Apache logs, update your LogFormat to reference them. Example LogFormat that includes the incoming proxy headers:
After editing Apache config:
Now tail the access log again — the proxy headers should appear in the entries (the X header values will appear in double quotes as shown):
Notes on header semantics:
  • X-Forwarded-For is typically a comma-separated chain of IPs representing each proxy hop; the original client IP is usually the left-most entry in a well-formed chain.
  • X-Real-IP commonly contains the immediate client’s IP as seen by the last proxy (useful if you only have a single proxy).
  • X-Forwarded-Proto indicates whether the original request used http or https.

Recap

  • Verified the site before adding headers and inspected response headers.
  • Added common security headers in the NGINX TLS server block: Strict-Transport-Security, X-Frame-Options, Content-Security-Policy, Referrer-Policy.
  • Configured an upstream with two backend Apache servers and added proxy_set_header directives so backends receive original client IP, forwarded chain, and protocol.
  • Updated Apache LogFormat to log these incoming proxy headers, so logs reflect accurate request origin and protocol.
Further reading and references: You can expand this setup with additional security headers, caching, compression, access controls, or Apache modules like mod_remoteip depending on your needs. Thanks, and happy configuring!

Watch Video

Practice Lab