{"id":1056,"date":"2026-10-05T11:18:32","date_gmt":"2026-10-05T11:18:32","guid":{"rendered":"https:\/\/www.vps.tc\/blog\/?p=1056"},"modified":"2026-10-05T09:50:51","modified_gmt":"2026-10-05T09:50:51","slug":"nginx-reverse-proxy-setup","status":"publish","type":"post","link":"https:\/\/www.vps.tc\/blog\/en\/nginx-reverse-proxy-setup\/","title":{"rendered":"How to Configure Nginx as a Reverse Proxy"},"content":{"rendered":"<div class=\"aiw-summary\" id=\"aiw-ozet\">\n<p class=\"aiw-summary-title\">Quick Summary &#8211; Nginx Reverse Proxy Setup<\/p>\n<p>Nginx can accept public HTTP and HTTPS traffic while keeping your application on a private localhost port. Reliable operation depends on correct DNS, trusted forwarded headers, tested reloads, and separate checks for TLS and WebSockets.<\/p>\n<ul>\n<li><strong>Check DNS first<\/strong> \u2014 Confirm the domain&#039;s A or AAAA record points to the VPS before changing Nginx.<\/li>\n<li><strong>Keep backends private<\/strong> \u2014 Bind applications to localhost or restrict remote backends to the proxy server&#039;s private IP.<\/li>\n<li><strong>Forward identity<\/strong> \u2014 Pass Host, client IP, and protocol headers while trusting them only from known proxy layers.<\/li>\n<li><strong>Test before reload<\/strong> \u2014 Run nginx -t before every reload and keep a copy of the previous configuration.<\/li>\n<li><strong>Handle WebSockets<\/strong> \u2014 Forward upgrade headers only where the application needs them and set timeouts deliberately.<\/li>\n<li><strong>Measure each layer<\/strong> \u2014 Use curl, Nginx logs, application logs, and timing variables to separate backend, proxy, and TLS failures.<\/li>\n<\/ul>\n<\/div>\n<p class=\"aiw-lead\">An Nginx reverse proxy gives you one controlled HTTP or HTTPS endpoint while your application stays on a private address such as 127.0.0.1:3000. The configuration is not difficult, but DNS, forwarded headers, WebSockets, certificate renewal, and reload testing are where a working proxy becomes a dependable one.<\/p>\n<div class=\"aiw-toc\" style=\"border:1px solid #dbe3ea;border-radius:8px;padding:16px 20px;margin:0 0 28px\">\n<p class=\"aiw-toc-head\"><strong>Table of Contents<\/strong><span class=\"aiw-toc-meta\"> \u00b7 13 min read<\/span><\/p>\n<ol style=\"margin:10px 0 0;padding-left:22px\">\n<li><a href=\"#what-does-an-nginx-reverse-proxy-do\">What does an Nginx reverse proxy do?<\/a><\/li>\n<li><a href=\"#what-should-you-prepare-before-configuring-the-proxy\">What should you prepare before configuring the proxy?<\/a><\/li>\n<li><a href=\"#setting-up-an-nginx-reverse-proxy-on-ubuntu\">Setting up an Nginx reverse proxy on Ubuntu<\/a><\/li>\n<li><a href=\"#forwarding-the-client-ip-and-proxy-headers\">Forwarding the client IP and proxy headers<\/a><\/li>\n<li><a href=\"#adding-https-and-hostname-redirects\">Adding HTTPS and hostname redirects<\/a><\/li>\n<li><a href=\"#websockets-and-long-lived-connections\">WebSockets and long-lived connections<\/a><\/li>\n<li><a href=\"#static-files-path-prefixes-and-multiple-applications\">Static files, path prefixes, and multiple applications<\/a><\/li>\n<li><a href=\"#investigating-errors-and-measuring-performance\">Investigating errors and measuring performance<\/a><\/li>\n<li><a href=\"#security-checks-before-going-live\">Security checks before going live<\/a><\/li>\n<li><a href=\"#check-these-before-going-live\">Check These Before Going Live<\/a><\/li>\n<li><a href=\"#frequently-asked-questions\">Frequently Asked Questions<\/a><\/li>\n<li><a href=\"#sources\">Sources<\/a><\/li>\n<\/ol>\n<\/div>\n<h2 id=\"what-does-an-nginx-reverse-proxy-do\">What does an Nginx reverse proxy do?<\/h2>\n<p>I usually notice a reverse proxy when it is missing. An application is listening on a public port, TLS is configured inside the application, logs are scattered across several services, and one small change requires touching three places. Nginx gives me a single public entry point while the application stays on <code>127.0.0.1:3000<\/code>, <code>127.0.0.1:8000<\/code>, or a private Unix socket.<\/p>\n<p>Nginx accepts HTTP requests on ports 80 and 443, then forwards them to the backend. That makes one place for TLS termination, hostname routing, access logs, and selected security rules.<\/p>\n<p>The installation looks like a few file edits. The parts that usually bite are forwarding the client IP correctly, keeping WebSocket connections alive, pointing DNS at the right server, and testing the configuration before reloading it.<\/p>\n<p>The distinction explained in <em><a href=\"https:\/\/www.vps.tc\/blog\/en\/domain-vs-url-difference\/\">Domain vs URL: What Is the Difference?<\/a><\/em> is useful here: Nginx chooses the <code>server<\/code> block from the hostname, while the path portion is usually handled by <code>location<\/code> rules.<\/p>\n<p><strong>Keep the backend private.<\/strong> If the application and Nginx share a VPS, bind the application to localhost whenever possible.<\/p>\n<h2 id=\"what-should-you-prepare-before-configuring-the-proxy\">What should you prepare before configuring the proxy?<\/h2>\n<p>The examples use Ubuntu 22.04 or 24.04, Nginx, and an application listening on port 3000. Package names and file layouts differ on other distributions. My guide <em><a href=\"https:\/\/www.vps.tc\/blog\/en\/install-configure-nginx-on-ubuntu\/\">How to Install and Configure Nginx on Ubuntu<\/a><\/em> covers the initial package setup; here I am focusing on the reverse proxy layer.<\/p>\n<p>First, verify that the domain&#8217;s A or AAAA record points to the VPS. I normally start in the terminal, not in a hosting panel:<\/p>\n<pre><code>dig +short example.com A\ndig +short example.com AAAA<\/code><\/pre>\n<p>The output should contain the VPS&#8217;s IPv4 or IPv6 address. If an old address comes back, changing Nginx will not fix the request path because traffic may still be reaching another server. DNS caching and propagation are discussed in <em><a href=\"https:\/\/www.vps.tc\/blog\/en\/what-is-dns-propagation-how-long-does-it-take\/\">What Is DNS Propagation? How Long Does It Take?<\/a><\/em>.<\/p>\n<p>Test the backend before putting Nginx in front of it:<\/p>\n<pre><code>ss -ltnp | grep ':3000'\ncurl --fail --silent --show-error http:\/\/127.0.0.1:3000\/<\/code><\/pre>\n<p>The <code>ss<\/code> output should show the application process listening on port 3000. The <code>curl<\/code> command should return a <code>2xx<\/code> response, or another status code that the application intentionally uses. If nothing is listening, fix the application service first.<\/p>\n<p><strong>Start with the backend.<\/strong> A proxy cannot repair an application that is not answering locally.<\/p>\n<div class=\"aiw-callout aiw-callout-tip\">\n<p class=\"aiw-callout-label\">Tip<\/p>\n<p>Use dig +short and curl against localhost before editing the Nginx site file. If DNS or the backend is wrong, a proxy change only moves the symptom.<\/p>\n<\/div>\n<h2 id=\"setting-up-an-nginx-reverse-proxy-on-ubuntu\">Setting up an Nginx reverse proxy on Ubuntu<\/h2>\n<h3>Install and enable the package<\/h3>\n<p>Install Nginx from Ubuntu&#8217;s repositories and check the service:<\/p>\n<pre><code>sudo apt update\nsudo apt install nginx\nsudo systemctl enable --now nginx\nsudo systemctl status nginx --no-pager<\/code><\/pre>\n<p>The default installation listens on port 80. If you use UFW, allow both HTTP and HTTPS:<\/p>\n<pre><code>sudo ufw allow 'Nginx Full'\nsudo ufw status verbose<\/code><\/pre>\n<p>This permits TCP ports 80 and 443. There is no reason to expose port 3000 publicly when the backend is bound to localhost.<\/p>\n<h3>Create the site configuration<\/h3>\n<p>On Debian-based systems, Nginx loads enabled sites through symbolic links in <code>\/etc\/nginx\/sites-enabled\/<\/code>. Create the file in <code>sites-available<\/code>:<\/p>\n<pre><code>sudo vim \/etc\/nginx\/sites-available\/example.com<\/code><\/pre>\n<p>This configuration forwards requests for <code>example.com<\/code> and <code>www.example.com<\/code> to port 3000:<\/p>\n<pre><code>server {\n    listen 80;\n    listen [::]:80;\n\n    server_name example.com www.example.com;\n\n    access_log \/var\/log\/nginx\/example.com.access.log;\n    error_log  \/var\/log\/nginx\/example.com.error.log warn;\n\n    location \/ {\n        proxy_pass http:\/\/127.0.0.1:3000;\n        proxy_http_version 1.1;\n\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n\n        proxy_read_timeout 60s;\n        proxy_connect_timeout 5s;\n    }\n}<\/code><\/pre>\n<p><code>proxy_pass<\/code> identifies the backend. The <code>Host<\/code> header preserves the hostname used by the client, while <code>X-Real-IP<\/code> and <code>X-Forwarded-For<\/code> carry address information upstream. These headers affect what the application believes about the request, so configure the application to trust forwarded values only from known proxy layers.<\/p>\n<p><code>proxy_read_timeout<\/code> controls how long Nginx waits for a response from the backend. Match it to the application&#8217;s real operations. Setting it to an arbitrary number of minutes can leave dead connections consuming resources.<\/p>\n<p>Create the symbolic link, remove the default site if you do not need it, test the configuration, and reload:<\/p>\n<pre><code>sudo ln -s \/etc\/nginx\/sites-available\/example.com \/etc\/nginx\/sites-enabled\/example.com\nsudo rm -f \/etc\/nginx\/sites-enabled\/default\nsudo nginx -t\nsudo systemctl reload nginx<\/code><\/pre>\n<p>Do not run <code>reload<\/code> until <code>nginx -t<\/code> succeeds. Successful output normally includes <code>syntax is ok<\/code> and <code>test is successful<\/code>. A reload normally applies the new configuration without stopping existing worker processes; <code>restart<\/code> stops and starts the service instead.<\/p>\n<p><strong>Test first.<\/strong> I run <code>sudo nginx -t<\/code> before every reload. That habit has saved me from turning a typo into an outage.<\/p>\n<h2 id=\"forwarding-the-client-ip-and-proxy-headers\">Forwarding the client IP and proxy headers<\/h2>\n<p>An application behind a reverse proxy sees the TCP connection as coming from Nginx. Forward the relevant headers when the application needs the original client address in logs, rate limits, redirects, or audit records.<\/p>\n<p><code>$proxy_add_x_forwarded_for<\/code> appends the address Nginx sees to an existing <code>X-Forwarded-For<\/code> chain. Using only <code>$remote_addr<\/code> can discard information from an earlier trusted proxy.<\/p>\n<p>If Nginx terminates HTTPS and sends plain HTTP to the backend, <code>X-Forwarded-Proto: https<\/code> matters. Without it, an application may generate <code>http:\/\/<\/code> URLs, omit the <code>Secure<\/code> attribute from session cookies, or create an endless HTTPS redirect.<\/p>\n<p>When Nginx sits behind a CDN or load balancer, define trusted proxy addresses separately. Do not let the application accept every incoming <code>X-Forwarded-For<\/code> value as fact. A header supplied directly by an internet client is not proof of identity.<\/p>\n<p>RFC 7239 defines the standardized <code>Forwarded<\/code> header. Many applications still expect <code>X-Forwarded-For<\/code>, <code>X-Forwarded-Proto<\/code>, and <code>X-Real-IP<\/code>, so I match the format to the application&#8217;s documentation instead of adding every possible header.<\/p>\n<p><strong>Define the trust boundary.<\/strong> Client IP headers are useful only when the application knows which proxy inserted them.<\/p>\n<div class=\"aiw-callout aiw-callout-warn\">\n<p class=\"aiw-callout-label\">Caution<\/p>\n<p>Do not trust every X-Forwarded-For value from the public internet. Define which proxy addresses are trusted, or your logs and access controls may use an attacker-supplied client IP.<\/p>\n<\/div>\n<h2 id=\"adding-https-and-hostname-redirects\">Adding HTTPS and hostname redirects<\/h2>\n<p>Once the HTTP path works, add TLS. Before requesting a certificate, DNS must point to the correct server and TCP port 80 must be reachable for an ACME HTTP-01 challenge. If Certbot or another tool edits Nginx automatically, I back up the existing configuration first.<\/p>\n<p>A basic HTTPS configuration looks like this:<\/p>\n<pre><code>server {\n    listen 443 ssl;\n    listen [::]:443 ssl;\n\n    server_name example.com www.example.com;\n\n    ssl_certificate     \/etc\/letsencrypt\/live\/example.com\/fullchain.pem;\n    ssl_certificate_key \/etc\/letsencrypt\/live\/example.com\/privkey.pem;\n\n    location \/ {\n        proxy_pass http:\/\/127.0.0.1:3000;\n        proxy_http_version 1.1;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n}\n\nserver {\n    listen 80;\n    listen [::]:80;\n    server_name example.com www.example.com;\n    return 301 https:\/\/$host$request_uri;\n}<\/code><\/pre>\n<p>The <code>301<\/code> redirect sends HTTP requests to HTTPS while preserving the path and query string. If the certificate files do not exist yet, <code>nginx -t<\/code> will fail, so do not enable the TLS block before issuing the certificate.<\/p>\n<p>Check that every hostname appears in the certificate. After renewal, configuration testing and an Nginx reload should be part of the renewal workflow. Renewing the files does not mean that an already-running Nginx process has loaded them.<\/p>\n<p><strong>Test the whole path.<\/strong> Check the certificate, hostname, redirect, and renewal job together instead of trusting only the padlock in a browser.<\/p>\n<h2 id=\"websockets-and-long-lived-connections\">WebSockets and long-lived connections<\/h2>\n<p>A basic proxy is enough for ordinary HTTP requests. WebSockets use an HTTP upgrade flow, so Nginx must pass the <code>Upgrade<\/code> and <code>Connection<\/code> headers to the backend.<\/p>\n<pre><code>map $http_upgrade $connection_upgrade {\n    default upgrade;\n    ''      close;\n}\n\nserver {\n    listen 443 ssl;\n    server_name app.example.com;\n\n    location \/socket.io\/ {\n        proxy_pass http:\/\/127.0.0.1:3000;\n        proxy_http_version 1.1;\n        proxy_set_header Upgrade $http_upgrade;\n        proxy_set_header Connection $connection_upgrade;\n        proxy_set_header Host $host;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n        proxy_read_timeout 3600s;\n    }\n}<\/code><\/pre>\n<p>The <code>map<\/code> block belongs in the <code>http<\/code> context, usually inside the <code>http {}<\/code> section of <code>\/etc\/nginx\/nginx.conf<\/code>. Putting it inside a site file can produce a context error. The WebSocket path may be <code>\/socket.io\/<\/code>, <code>\/ws\/<\/code>, or something else in your application.<\/p>\n<p>A long timeout does not prove that a connection is healthy. The application should send heartbeats, and monitoring should track open connection counts and error rates. <em><a href=\"https:\/\/www.vps.tc\/blog\/en\/prometheus-grafana-setup-vps-monitoring\/\">How to Monitor a VPS with Prometheus and Grafana<\/a><\/em> covers the basic approach for making those metrics visible.<\/p>\n<p><strong>Limit upgrades.<\/strong> Enable the upgrade headers only in locations that genuinely serve WebSockets.<\/p>\n<h2 id=\"static-files-path-prefixes-and-multiple-applications\">Static files, path prefixes, and multiple applications<\/h2>\n<p>You can run several applications on one VPS. Nginx selects a separate <code>server<\/code> block for each hostname, and different paths on one hostname can go to different upstreams.<\/p>\n<pre><code>upstream api_backend {\n    server 127.0.0.1:8000;\n    keepalive 16;\n}\n\nserver {\n    listen 443 ssl;\n    server_name example.com;\n\n    root \/var\/www\/example.com\/public;\n\n    location \/assets\/ {\n        try_files $uri =404;\n        access_log off;\n        expires 7d;\n    }\n\n    location \/api\/ {\n        proxy_pass http:\/\/api_backend;\n        proxy_set_header Host $host;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n\n    location \/ {\n        proxy_pass http:\/\/127.0.0.1:3000;\n        proxy_set_header Host $host;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n}<\/code><\/pre>\n<p>Using <code>proxy_pass<\/code> with or without a URI changes the path sent upstream. With <code>location \/api\/<\/code>, <code>proxy_pass http:\/\/127.0.0.1:8000;<\/code> preserves the request path. Adding a trailing slash, as in <code>proxy_pass http:\/\/127.0.0.1:8000\/;<\/code>, changes the matching part of the URI. Test whether the API expects <code>\/api\/users<\/code> or <code>\/users<\/code> with a small <code>curl<\/code> request.<\/p>\n<p><code>try_files $uri =404;<\/code> prevents missing static files from being sent needlessly to the application. Set the cache lifetime together with your file-naming strategy; long <code>expires<\/code> values become painful when changed content keeps the same URL.<\/p>\n<p><strong>Check both paths.<\/strong> Test the path the client requests and the path the backend actually receives for every <code>location<\/code> block.<\/p>\n<h2 id=\"investigating-errors-and-measuring-performance\">Investigating errors and measuring performance<\/h2>\n<p>When a proxy fails, I do not restart Nginx immediately. I separate three layers: client to Nginx, Nginx to the backend, and the application&#8217;s own logs.<\/p>\n<p>These checks narrow the problem quickly:<\/p>\n<pre><code>curl -I http:\/\/127.0.0.1:3000\/\ncurl -I -H 'Host: example.com' http:\/\/127.0.0.1\/\ncurl -vk https:\/\/example.com\/\nsudo tail -f \/var\/log\/nginx\/example.com.error.log\nsudo journalctl -u nginx -n 100 --no-pager<\/code><\/pre>\n<p>The first command checks the application, the second checks selection of the correct <code>server_name<\/code>, and the third shows TLS negotiation and redirects. A <code>502 Bad Gateway<\/code> usually means Nginx could not connect to the backend, the application is down, or the port or socket is wrong. A <code>504 Gateway Timeout<\/code> usually means the backend did not respond in time. Increasing the timeout before reading the application log is not a diagnosis.<\/p>\n<p>I once treated a timeout change as the fix for a slow backend. The application was actually returning errors before the timeout mattered. Since then I read the Nginx and application logs first, then measure the request path again.<\/p>\n<p>Access logs can show request and upstream timing. For more detail, define a format in the <code>http<\/code> context:<\/p>\n<pre><code>log_format proxy_timing '$remote_addr - $host [$time_iso8601] '\n                        '\"$request\" $status $body_bytes_sent '\n                        'rt=$request_time '\n                        'uct=$upstream_connect_time '\n                        'urt=$upstream_response_time';<\/code><\/pre>\n<p>Apply it to the relevant <code>access_log<\/code> directive. Keep raw logs for too long and the disk eventually becomes part of the incident. I check log rotation and disk alerts together. The two are related.<\/p>\n<p>If you put a CDN in front of Nginx, client IPs, cache headers, and HTTPS redirects can differ across the two proxy layers. <em><a href=\"https:\/\/www.vps.tc\/blog\/en\/what-is-a-cdn-how-it-speeds-up-websites\/\">What Is a CDN? How It Speeds Up Websites<\/a><\/em> explains the distinction between the cache layer and the origin server.<\/p>\n<p><strong>Measure in order.<\/strong> Test the backend, then Nginx, then TLS and any CDN layer separately.<\/p>\n<div class=\"aiw-callout aiw-callout-example\">\n<p class=\"aiw-callout-label\">Example<\/p>\n<p>A 502 usually points to a connection problem between Nginx and the backend, while a 504 usually means the backend did not answer within the configured time. Check the application log before increasing a timeout.<\/p>\n<\/div>\n<h2 id=\"security-checks-before-going-live\">Security checks before going live<\/h2>\n<p>A reverse proxy hides the application port, but it is not a firewall by itself. Administration panels, databases, and debug endpoints should not be exposed to the internet. Binding the application to <code>127.0.0.1<\/code> is a good start; for a backend on another server, allow only the Nginx server&#8217;s private IP in that server&#8217;s firewall.<\/p>\n<p>Do not add security headers as a fixed recipe without checking what the application expects. Define allowed hostnames explicitly in applications that use <code>Host<\/code> as input. For upload services, if Nginx&#8217;s <code>client_max_body_size<\/code> is lower than the application limit, clients receive <code>413 Request Entity Too Large<\/code>. Increasing a PHP or application limit does not change Nginx&#8217;s limit automatically.<\/p>\n<p>Keep configuration permissions as narrow as practical. A certificate private key should be readable only by root and any service account that genuinely needs it. Changing the SSH port or running <code>chmod 777<\/code> is not a substitute for proper access controls.<\/p>\n<p>Back up the configuration before deployment:<\/p>\n<pre><code>sudo cp -a \/etc\/nginx \/etc\/nginx.backup.$(date +%F-%H%M)\nsudo nginx -t\nsudo systemctl reload nginx\ncurl --fail --silent --show-error -o \/dev\/null -w '%{http_code}\\n' https:\/\/example.com\/<\/code><\/pre>\n<p>The final <code>curl<\/code> command prints only the HTTP status code. It does not prove that the response body is correct, so test login, static files, API requests, and WebSockets separately for a critical release.<\/p>\n<p>Keep the previous configuration nearby. A rollback you can execute calmly is part of the setup, not an admission that the deployment failed.<\/p>\n<div class=\"aiw-callout aiw-callout-field\">\n<p class=\"aiw-callout-label\">From the field<\/p>\n<p>I once increased a proxy timeout before checking the application logs. The timeout was not the problem; the backend was already failing quickly. Since then I test the backend, Nginx, and TLS as separate layers.<\/p>\n<\/div>\n<h2 id=\"check-these-before-going-live\">Check These Before Going Live<\/h2>\n<ul class=\"aiw-checklist\">\n<li>Confirm that A and AAAA records point to the correct VPS.<\/li>\n<li>Verify that the backend responds successfully on localhost.<\/li>\n<li>Install the site configuration and create its symbolic link.<\/li>\n<li>Run nginx -t before every reload.<\/li>\n<li>Forward only the headers your application understands and trust.<\/li>\n<li>Test HTTPS, redirects, certificate renewal, and WebSocket paths.<\/li>\n<li>Keep a configuration backup and verify the rollback command.<\/li>\n<\/ul>\n<div class=\"aiw-cta\">\n<p>Start with one hostname and one localhost backend. Test each layer, then add HTTPS, WebSockets, or path routing after the simple proxy works. Keep the previous configuration within reach; I still do this even for changes that look too small to fail.<\/p>\n<p class=\"aiw-cta-action\"><a href=\"https:\/\/www.vps.tc\/en\/vps\">Explore VPS plans<\/a><\/p>\n<\/div>\n<h2 id=\"frequently-asked-questions\">Frequently Asked Questions<\/h2>\n<div class=\"aiw-faq\">\n<details class=\"aiw-faq-item\" open>\n<summary>What is an Nginx reverse proxy?<\/summary>\n<p>An Nginx reverse proxy accepts requests from clients and forwards them to an application server. The application can listen on a private address such as 127.0.0.1:3000, while Nginx handles public ports, hostnames, TLS, access logs, and selected security rules. Clients communicate with Nginx rather than connecting directly to the backend.<\/p>\n<\/details>\n<details class=\"aiw-faq-item\">\n<summary>Should my application listen on port 3000 publicly?<\/summary>\n<p>Usually no. If Nginx and the application run on the same VPS, bind the application to 127.0.0.1 so outside clients cannot reach it directly. If the backend is on another server, restrict its firewall to the Nginx server&#039;s private address. Exposing the application port creates another public attack surface and bypasses controls configured at Nginx.<\/p>\n<\/details>\n<details class=\"aiw-faq-item\">\n<summary>Why does Nginx return 502 Bad Gateway?<\/summary>\n<p>A 502 commonly means Nginx cannot connect to the configured backend. Check whether the application is running, whether it is listening on the expected port or socket, and whether the address in proxy_pass is correct. Test the backend directly with curl, then inspect the Nginx error log and the application&#039;s own log before changing timeouts.<\/p>\n<\/details>\n<details class=\"aiw-faq-item\">\n<summary>How do I configure WebSockets through Nginx?<\/summary>\n<p>Use HTTP\/1.1 and forward the Upgrade and Connection headers in the location that serves the WebSocket endpoint. A map block can select upgrade or close based on the incoming request. Put that map in the http context, not inside a server or location block, and use a read timeout appropriate for the application&#039;s heartbeat behavior.<\/p>\n<\/details>\n<details class=\"aiw-faq-item\">\n<summary>Do I need to configure X-Forwarded-For?<\/summary>\n<p>Configure it when the application needs the original client address for logs, redirects, rate limits, or auditing. Use $proxy_add_x_forwarded_for when preserving a trusted proxy chain, and configure the application to trust headers only from known proxies. Never accept arbitrary public X-Forwarded-For values as authenticated client identity.<\/p>\n<\/details>\n<details class=\"aiw-faq-item\">\n<summary>Can Nginx terminate HTTPS and send HTTP to the application?<\/summary>\n<p>Yes. Nginx can terminate TLS on port 443 and proxy plain HTTP to a backend on localhost. Pass X-Forwarded-Proto so the application knows the original request used HTTPS; otherwise it may generate insecure URLs, omit Secure cookies, or redirect repeatedly. Test certificate renewal and reload behavior rather than assuming renewed files are loaded automatically.<\/p>\n<\/details>\n<\/div>\n<h2 id=\"sources\">Sources<\/h2>\n<ul class=\"aiw-sources\">\n<li><a href=\"https:\/\/nginx.org\/en\/docs\/\" target=\"_blank\" rel=\"noopener\">Nginx Documentation<\/a> \u2014 nginx.org<\/li>\n<li><a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc7239\" target=\"_blank\" rel=\"noopener\">RFC 7239 &#8211; Forwarded HTTP Extension<\/a> \u2014 rfc-editor.org<\/li>\n<li><a href=\"https:\/\/letsencrypt.org\/docs\/challenge-types\/\" target=\"_blank\" rel=\"noopener\">Let&#039;s Encrypt &#8211; Challenge Types<\/a> \u2014 letsencrypt.org<\/li>\n<li><a href=\"https:\/\/documentation.ubuntu.com\/server\/how-to\/web-services\/install-nginx\/\" target=\"_blank\" rel=\"noopener\">Ubuntu Server Documentation &#8211; Install Nginx<\/a> \u2014 documentation.ubuntu.com<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Configure Nginx as a reverse proxy on Ubuntu, forward client headers, add HTTPS and WebSocket support, route multiple applications, and troubleshoot common errors.<\/p>\n","protected":false},"author":2,"featured_media":1054,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2],"tags":[],"class_list":["post-1056","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-hosting"],"lang":"en","translations":{"en":1056,"tr":1055},"pll_sync_post":[],"_links":{"self":[{"href":"https:\/\/www.vps.tc\/blog\/wp-json\/wp\/v2\/posts\/1056","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.vps.tc\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.vps.tc\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.vps.tc\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.vps.tc\/blog\/wp-json\/wp\/v2\/comments?post=1056"}],"version-history":[{"count":1,"href":"https:\/\/www.vps.tc\/blog\/wp-json\/wp\/v2\/posts\/1056\/revisions"}],"predecessor-version":[{"id":1058,"href":"https:\/\/www.vps.tc\/blog\/wp-json\/wp\/v2\/posts\/1056\/revisions\/1058"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.vps.tc\/blog\/wp-json\/wp\/v2\/media\/1054"}],"wp:attachment":[{"href":"https:\/\/www.vps.tc\/blog\/wp-json\/wp\/v2\/media?parent=1056"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.vps.tc\/blog\/wp-json\/wp\/v2\/categories?post=1056"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.vps.tc\/blog\/wp-json\/wp\/v2\/tags?post=1056"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}