October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PCOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
VGSources
Blog

How to Set Up an NGINX Load Balancer With Private Networking

A practical guide to building an NGINX load balancer whose listener and backends stay on private IPs, including upstream configuration, firewall rules, TLS, health checks, and validation.
Length14 min Posted Quest giverVGSources Team
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Set up NGINX on a private subnet, define an upstream group containing the backends’ private addresses, and send requests to that group with proxy_pass. Keep backend instances without public IP addresses, allow their application and health-check ports only from the NGINX security identity, and verify that routes and return paths exist across the VPC or VNet. For HTTP and HTTPS, configure the http context; for TCP or UDP services, use the stream module.

The configuration is straightforward. The difficult parts are network reachability, firewall scope, health behavior, and deciding whether the private hop should also use TLS. This guide covers a complete design, working configuration patterns, validation, and the failure modes that commonly make a private load balancer appear broken.

As an Amazon Associate I earn from qualifying purchases.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What a private NGINX load balancer does

NGINX is the client-facing proxy and traffic distributor. Clients connect to its listener, while NGINX opens a separate connection to one server in an upstream group. With no method specified, NGINX uses round-robin distribution. You can instead use least connections, IP hash, generic hashing, weights, backup servers, and passive failure handling when the workload requires them.

“Private” describes addressing and reachability, not a special NGINX mode. A private deployment normally has all of these properties:

  • NGINX listens on a private address, or an internal cloud load balancer exposes NGINX only inside the virtual network.
  • Backends use private IP addresses or private DNS names and do not need public IP addresses.
  • Route tables, peering, VPN, or Private Link provide a path from NGINX to every backend and a return path back to NGINX.
  • Security groups, network security groups, subnet ACLs, and host firewalls restrict backend ports to NGINX and the required probe sources.

A private address alone does not guarantee isolation. A permissive route or firewall can still expose a service to a broad network, and a missing return route can make a healthy backend unreachable.

Choose the network layout before writing NGINX configuration

Same virtual network

The simplest design places NGINX and the application servers in the same VPC, VNet, or connected subnet plan. Give NGINX a private address such as 10.0.1.10 and place application nodes in private subnets such as 10.0.2.0/24 and 10.0.3.0/24. Ensure the subnet route tables contain local routes for those ranges.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Peered or VPN-connected networks

If the backends live in another virtual network or data center, use peering or a VPN and add routes in both directions. Check for overlapping CIDR ranges before creating the connection. A route from NGINX to a backend is only half the requirement: the backend’s response must be routed back to NGINX’s private address.

Private Link and internal cloud load balancers

Provider-specific private connectivity can expose a service over the provider’s backbone without sending traffic across the public internet. Azure documentation describes Private Link in these terms and notes that virtual-network peering may preserve more load-balancing capabilities for some designs. In AWS, an internal load balancer has only private IP addresses, and its targets do not need public IP addresses. Clients must already have access to the load balancer’s VPC.

Private DNS

Prefer private DNS names when addresses can change during scaling or replacement. For example, use app-1.internal.example and app-2.internal.example, or a service-discovery name that resolves only inside the network. Confirm the resolver used by the NGINX host can resolve the private records. If you use names in an upstream and expect their addresses to change, plan DNS caching and reload behavior deliberately; a static configuration does not automatically discover every provider-side address change.

Prerequisites and access rules

Prepare these items before editing nginx.conf:

  • An NGINX host with the HTTP proxy module for web traffic. For databases, DNS, LDAP, or other non-HTTP protocols, NGINX must include the stream module, either compiled in or loaded as a module.
  • A private listener address and the exact port clients should use, such as TCP 443 for HTTPS or TCP 5432 for PostgreSQL.
  • Private backend addresses or private DNS names and their listening ports.
  • A certificate and private key for a TLS listener, if NGINX terminates HTTPS.
  • Network routes and return routes between NGINX and every backend.
  • Firewall rules that distinguish client-to-NGINX traffic, NGINX-to-backend traffic, and health-probe traffic.
  • Administrative access to test from the NGINX host and from an allowed client network.

Do not start by opening the backend port to an entire VPC or VNet unless that broad access is an explicit requirement. A tighter rule allows the backend listener only from the NGINX security-group identity, NSG identity, or a narrowly defined private source range.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Configure HTTP or HTTPS private upstreams

1. Define the upstream group

Place the upstream in the http context. Each server line includes a private address and an explicit application port. The following example uses illustrative addresses and values:

http {
    upstream app_private {
        server 10.0.2.11:8080 max_fails=3 fail_timeout=10s;
        server 10.0.3.11:8080 max_fails=3 fail_timeout=10s;
        # least_conn;       # Uncomment for uneven connection durations
        # ip_hash;          # Uncomment only when client affinity is required
    }

    server {
        listen 10.0.1.10:443 ssl;
        server_name app.internal.example;

        ssl_certificate     /etc/nginx/tls/app.crt;
        ssl_certificate_key /etc/nginx/tls/app.key;

        location / {
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_pass http://app_private;
        }
    }
}

The proxy_pass directive references the upstream group by name. NGINX selects a member according to the configured method, opens a connection to that member’s private address, and returns the upstream response to the client.

2. Bind the listener to the intended private address

listen 10.0.1.10:443 ssl; prevents accidental binding to every interface on the host. If the machine has multiple interfaces, verify that the address belongs to the private interface and that the route table sends client traffic there. If a separate internal cloud load balancer fronts NGINX, bind NGINX to the private address on which that load balancer sends traffic.

3. Preserve useful request identity

The header directives pass the original host name and client address information to the application. X-Forwarded-For appends the current client address to an existing chain, while X-Forwarded-Proto tells the application whether the client used HTTP or HTTPS at the edge. Configure the application to trust these headers only from NGINX; otherwise an external client could forge them.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

4. Decide whether the edge is HTTP or HTTPS

For an HTTPS listener, install a certificate whose name matches the private hostname clients use. Private DNS does not remove certificate-name requirements. You can also listen on private HTTP when encryption is provided elsewhere, but document that trust boundary and ensure no untrusted network can reach that port.

Choose the upstream balancing method

Method How it behaves Use it when Main caution
Round robin Sends successive requests across available servers; this is the default. Nodes are comparable and requests have roughly similar cost. It does not account for a long-running connection already occupying a node.
Least connections Chooses the server with the fewest active connections. Connections remain open for different lengths of time, such as streaming or long polling. It may not reflect request CPU cost when short requests vary greatly in complexity.
IP hash Uses the client address to provide basic affinity. An application requires a client to return to the same node and cannot share session state. Distribution becomes uneven when many clients share one NAT address or when client addresses change.
Generic hash Hashes a selected key, such as a cookie or request attribute. You need affinity based on a value other than the source IP. Choose and normalize the key carefully; a low-cardinality key can overload one node.
Weights Assigns proportionally more traffic to larger or faster nodes. Backend capacity is intentionally unequal. Weights are routing policy, not a measurement of live capacity.
Backup servers Uses a standby only when regular servers are unavailable. You need a warm failover node rather than normal traffic on every node. Recovery capacity and readiness of the standby must be tested.
Passive failure handling max_fails and fail_timeout temporarily avoid a server after repeated unsuccessful attempts. You want basic failure avoidance without active probe features. It reacts to proxied traffic and is not a substitute for an application-aware health check.

For the example above, leave round robin enabled when the servers are equivalent. Add least_conn; inside the upstream block when connection duration is the dominant source of imbalance. Add ip_hash; only after confirming that session affinity is necessary.

Encrypt and verify the NGINX-to-backend hop

A private route reduces exposure but does not automatically encrypt traffic. Use HTTPS to the upstream when the network crosses trust boundaries, when policy requires encryption in transit, or when credentials and personal data must not be readable on the private segment.

location / {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    proxy_pass https://app_private;
    proxy_ssl_trusted_certificate /etc/nginx/tls/internal-ca.pem;
    proxy_ssl_verify on;
    proxy_ssl_verify_depth 2;
    proxy_ssl_protocols TLSv1.2 TLSv1.3;
}

Changing proxy_pass to https:// enables TLS from NGINX to the upstream. proxy_ssl_trusted_certificate identifies the CA bundle used to validate the backend certificate, and proxy_ssl_verify on; makes validation mandatory. Set the verification depth and protocol versions to match your certificate hierarchy and security policy.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The upstream certificate must be valid for the name NGINX verifies. If the backend certificate is issued to a DNS name but the upstream is configured only with an IP address, configure the appropriate upstream name and certificate-name behavior rather than disabling verification. Client certificates can be added when the backend requires mutual TLS; store the key with permissions that prevent ordinary users from reading it.

Configure TCP or UDP services with the stream module

HTTP directives cannot proxy a database or other arbitrary protocol. Use a stream block and specify backend ports explicitly:

stream {
    upstream db_private {
        server 10.0.2.21:5432;
        server 10.0.3.21:5432;
    }

    server {
        listen 10.0.1.10:5432;
        proxy_connect_timeout 5s;
        proxy_timeout 60s;
        proxy_pass db_private;
    }
}

In the stream configuration, the protocol is established by the listening port and the stream context. The proxy_pass value is the upstream group name, not an HTTP URL with a scheme. Set proxy_connect_timeout low enough to fail over promptly when a node is unreachable, and choose proxy_timeout according to the expected idle period of the protocol. A timeout that is too short can terminate legitimate database sessions; one that is too long can retain dead connections.

UDP requires additional protocol-specific planning because it is connectionless and may need session or response affinity. Confirm that the NGINX build and stream configuration support the UDP behavior your service expects, then test packet flow and response routing separately from TCP.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Build firewall and security-group rules

Client to NGINX

Allow only the client CIDRs that should reach the private listener, and open only the required ports. For an internal HTTPS service this is commonly TCP 443 from application subnets, corporate networks, or a connected VPN. Do not expose the listener to 0.0.0.0/0 unless a public edge is intentional and separately protected.

NGINX to backend

Permit the application port from the NGINX security-group or NSG identity. If identity-based rules are unavailable, use the smallest stable private source ranges that contain NGINX and nothing else. Apply the rule to every backend subnet and host firewall, not just the cloud-level security group.

Health probes

Health checks are a separate traffic flow. If an internal cloud load balancer probes NGINX or a backend, permit the provider’s documented probe source. Azure uses the AzureLoadBalancer service tag for health probes; host firewall policy must also account for the platform address 168.63.129.16 where applicable. Do not infer that an application rule automatically permits probes.

Network controls beyond security groups

Inspect subnet network ACLs, route tables, NSGs, host firewalls, and any appliance between the networks. A successful SYN from a client to NGINX proves only that the client-to-NGINX path works. It says nothing about NGINX’s connection to the upstream or the backend’s return route.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Validate the deployment in a controlled order

  1. Check the rendered configuration. Run nginx -t. If your installation supports it, inspect the expanded configuration with nginx -T and confirm that the intended upstream, listener address, certificate paths, and module directives are present.
  2. Reload safely. Use the service manager for the host. On a systemd-based Linux host, the usual command is sudo systemctl reload nginx; other operating systems and packaging systems use different commands. A reload should preserve existing connections while workers adopt the valid configuration.
  3. Test each backend from the NGINX host. Check DNS resolution, then open the exact private port. For HTTP, request the backend directly with the expected Host header. For HTTPS, validate the certificate chain and name. For TCP services, use a protocol-appropriate client rather than an HTTP tool.
  4. Test the private listener from an allowed client network. Confirm that the private hostname resolves to the intended address, that the certificate name matches, and that the application receives the expected host and forwarding headers.
  5. Exercise failure handling. Stop or isolate one backend and send multiple requests. Healthy peers should continue serving traffic after the failed node reaches the passive failure threshold. Restore the node and observe when it becomes eligible again.
  6. Review logs and counters. Examine access and error logs for upstream status, connect time, response time, selected peer, and connection failures. Alert on sustained upstream errors rather than a single transient failure.
  7. Verify the negative cases. Attempt a connection from an unauthorized network, to a backend port that should be closed, and to any public address that should not exist. Confirm that those attempts are denied.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshoot by locating the broken hop

Clients cannot connect to NGINX

  • Confirm the private DNS record resolves to the listener address.
  • Check that NGINX is listening on the expected interface and port with the host’s socket inspection tool.
  • Review the client route, subnet ACL, security group, NSG, and host firewall.
  • Check certificate and SNI behavior if TCP connects but HTTPS negotiation fails.

NGINX returns gateway errors

  • From the NGINX host, test each backend address and port independently.
  • Verify that the backend process is listening on the private interface, not only on loopback.
  • Inspect backend firewall rules for the actual NGINX source address or security identity.
  • Check route tables and return routes, including peering or VPN propagation.
  • For upstream TLS, verify the CA bundle, certificate name, expiration, protocol versions, and whether the backend requires a client certificate.

One server receives all traffic

  • Check whether ip_hash, a generic hash, or a weight is intentionally biasing distribution.
  • Confirm that the other servers are not being marked unavailable by max_fails and fail_timeout.
  • Look at connection duration; long-lived connections can make a round-robin request count look uneven.
  • Check whether clients share one NAT address, which can make IP-based affinity concentrate traffic.

Health probes fail while application requests work

  • Identify the probe source and permit it separately from normal client traffic.
  • Ensure the probe path returns the status code the cloud load balancer expects.
  • Check host firewall rules for provider probe addresses, including Azure’s platform address where relevant.
  • Make sure the probe reaches the same interface and port that NGINX or the backend actually listens on.

Operational and security hardening

Keep private backends truly private

Remove public IP addresses from backend instances when they are not required. If an operating-system update process needs internet access, provide controlled egress through a NAT or approved proxy rather than adding public ingress. Separate application, management, and monitoring subnets where practical.

Use least privilege for configuration and keys

Restrict ownership and permissions on ssl_certificate_key, upstream client keys, and internal CA files. Rotate certificates before expiration and reload only after nginx -t succeeds. Keep management access on a separate administrative path instead of exposing SSH or similar services on the application listener.

Make failures observable

Log the upstream address, status, connect time, and response time needed to distinguish DNS, routing, refusal, timeout, and application errors. Monitor certificate expiration, listener availability, backend failure counts, and health-probe status. Passive failure handling removes a peer temporarily; it does not tell you why the peer failed, so pair it with application and infrastructure alerts.

Plan capacity and connection limits

Account for connections from clients to NGINX and a second set of connections from NGINX to backends. A backend may see NGINX’s private address rather than the original client address at the network layer. Size file descriptors, worker connections, backend connection pools, and ephemeral ports for both legs, and test peak concurrency with the actual request and connection patterns.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Decide which private design fits your service

Design choice Advantages Trade-offs
Private NGINX listener and private backends Entire service stays inside the connected private network; backend exposure is minimized. Every client needs network access, private DNS, and correct routes.
Public edge with private backends Internet clients can reach the service while application nodes remain off the public internet. The public edge needs stronger TLS, rate limiting, abuse controls, and careful source-header trust.
Same VPC/VNet routing Fewer moving parts and simpler security rules. Less suitable when ownership or address space is divided across networks.
Peering or VPN Connects existing networks without moving workloads. Requires non-overlapping ranges, route propagation, and return-path testing.
Private Link Provides provider-backbone connectivity and avoids public exposure. Capabilities and source-address behavior vary by provider and service.
Plaintext private hop Simpler certificate management and lower configuration overhead. Anyone able to observe the private segment may read traffic.
Verified upstream TLS Encrypts and authenticates NGINX-to-backend traffic. Requires CA distribution, certificate rotation, and correct name verification.

For a conventional internal web application, start with a private NGINX listener, private DNS, round-robin upstreams, backend rules that accept only NGINX, and verified upstream TLS whenever the private network is not a fully trusted boundary. Add least connections for uneven connection lifetimes and affinity only when the application cannot externalize session state.

Frequently Asked Questions

Do backend servers need public IP addresses when NGINX is private?

No. Backends can use only private addresses as long as NGINX has a route to them, their return route works, and firewall rules permit the required traffic. AWS specifically describes internal load-balancer targets as not requiring public IP addresses.

Should I use round robin or least connections?

Use round robin when nodes are comparable and requests have similar cost. Use least_conn when connections remain open for uneven lengths of time. Test the real workload before adding affinity or weights.

Is a private network enough to skip TLS between NGINX and the backend?

Not always. Private routing limits exposure but does not authenticate or encrypt the hop. Use proxy_pass https://... with a trusted CA and certificate verification when policy or threat model requires encryption in transit.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Why can NGINX reach its listener but not an upstream?

The two paths are independent. Check backend listening state, NGINX source rules, subnet ACLs, route tables, peering or VPN routes, host firewalls, and the backend return path.

The Bottom Line

A reliable private NGINX load balancer is a network design plus an upstream configuration: private listener, private DNS or addresses, explicit routes in both directions, narrowly scoped backend and probe rules, and validation from each side of the connection. Configure balancing and TLS to match the workload, then test peer failure and denied paths before declaring the deployment complete.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More quests from Patch Notes

  1. How to Set Up a RedM (RDR2) Server in 2026: License Key, txAdmin and server.cfg, Step by StepBlog11min
  2. Best RedM (RDR2) Server Hosting in 2026: Comparing Four Hosts on Slots, Memory and PriceBlog10min
  3. How to Host a Mindustry Server in 2026: server-release.jar, Port 6567 and the Commands That MatterBlog7min
Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.