Quick answer
Putting a proxy in front of your site only protects you while attackers do not know the address behind it. If your origin IP is discoverable, an attacker can send traffic straight to it and every protection you are paying for is bypassed. Hiding it properly takes two things: removing the places it leaks from, and firewalling the server so it only accepts traffic from your provider, because the second one keeps working even after the first one fails.
01Why this matters more than it sounds
A reverse proxy protects you by being the only reachable path to your application. Traffic goes to the proxy, gets inspected, and only what survives is forwarded. Attackers know this, so the first step of a serious attack is often not sending traffic at all. It is finding the address behind the proxy.
Once they have it, everything downstream is irrelevant. Your WAF rules, rate limits and mitigation capacity are all attached to a path the attacker no longer uses.
The search is automated and cheap. Open-source tools such as CloudFlair take internet-wide scan data, look for servers presenting a TLS certificate for your domain, and then compare each candidate's page with your real site. Its authors name the root cause plainly: origins that are publicly reachable and do not restrict access to the proxy's address ranges.
02The usual ways an origin leaks
Almost every exposure is historical or incidental rather than a live misconfiguration, which is why it survives so long unnoticed.
- Old DNS records: an A or AAAA record for a subdomain you stopped using, still pointing at the real server
- Unproxied subdomains: mail, ftp, cpanel, direct, dev and staging are the classics
- A forgotten IPv6 address: the A record is proxied, but an AAAA record points straight at the machine
- SPF records: an ip4 or ip6 entry in your SPF TXT record names the server that sends your mail, which is often the web server
- Mail headers: messages sent from the same machine carry its address in their Received headers
- Certificate transparency logs: every publicly trusted certificate is logged, so a certificate for an origin-only hostname reveals that it exists
- The default certificate: internet-wide scanners connect to every address on port 443 and record the certificate they get back, so an origin that hands your real certificate to anyone is indexed against its IP
- Outbound requests: webhooks, pingbacks, link previews and image fetchers make your server connect to an address an attacker controls
- Error pages and debug output that print internal hostnames or addresses
- Historical DNS databases that keep records long after you change them
03Find your own leaks first
Do the attacker's homework before they do. Start with DNS: list every name you publish, in both address families, and confirm that each one resolves to your provider rather than to your server. A browser-based DNS lookup works for spot checks; the loop below does the same for a list of names, and also prints your MX and SPF records.
Then search certificate transparency for your domain, since public CT search services list the certificates logged for it, and check which certificate each hostname actually serves with an SSL checker. Finally, test the origin directly from a network outside your provider. Once the lock-down below is in place, that request must time out.
# every name you publish, both address families
for h in example.com www.example.com mail.example.com dev.example.com; do
printf '%-22s %s\n' "$h" "$(dig +short "$h" A "$h" AAAA | tr '\n' ' ')"
done
dig +short MX example.com
dig +short TXT example.com | grep -i spf
# from outside your provider: this should time out, not return your site
curl -sv --connect-timeout 5 -o /dev/null \
--resolve www.example.com:443:203.0.113.10 https://www.example.com/04The step that actually protects you
Everything above reduces the chance of discovery. The measure that survives discovery is refusing traffic from anywhere except your provider.
Configure the origin firewall to accept connections on your web ports only from your provider's published address ranges, for IPv4 and IPv6, and drop everything else. An attacker who learns your address then finds a server that will not talk to them, and the leak loses most of its value. What a firewall on the server cannot refuse is a flood large enough to fill its uplink, as whether a firewall can stop a DDoS attack explains, which is why a published address should still be changed once its leak is fixed.
Keep the range list current, because providers add ranges over time, and lock down SSH and other management ports separately, ideally to a VPN or a fixed admin address. The example also covers UDP on port 443, so an origin that answers HTTP/3 is not left open by accident.
# replace the documentation ranges with your provider's published list
table inet origin_lockdown {
set proxy_v4 {
type ipv4_addr
flags interval
elements = { 192.0.2.0/24, 198.51.100.0/24 }
}
set proxy_v6 {
type ipv6_addr
flags interval
elements = { 2001:db8::/32 }
}
chain input {
type filter hook input priority filter; policy accept;
iif "lo" accept
ct state established,related accept
meta l4proto { tcp, udp } th dport { 80, 443 } ip saddr @proxy_v4 accept
meta l4proto { tcp, udp } th dport { 80, 443 } ip6 saddr @proxy_v6 accept
meta l4proto { tcp, udp } th dport { 80, 443 } counter drop
}
}05Stop the bare address from answering
A firewall is the real control, but it is easy to apply to one address family and forget the other. As a second layer, make the web server refuse anything that does not ask for one of your hostnames. A scanner that connects to the bare address then gets no certificate and no page to compare.
In nginx 1.19.4 and later, ssl_reject_handshake in a default server rejects TLS handshakes for names you have not configured, and return 444 closes plain HTTP connections without sending a response. Your real sites keep their own server blocks and certificates.
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 444;
}
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
ssl_reject_handshake on;
}06A practical checklist
- Audit every DNS record, A and AAAA, including subdomains you forgot about; anything unproxied is a live exposure
- Send mail through a separate provider so neither headers nor SPF records carry your web server's address
- Avoid public certificates for origin-only hostnames, and use DNS validation so renewals never need the origin to be reachable
- Turn off debug output and verbose error pages in production
- Send outbound fetches such as webhooks, previews and pingbacks from a separate address, or disable the ones you do not use
- Restrict the origin firewall to your provider's ranges and verify it by connecting directly from outside
- Search historical DNS databases for your domain to see what is already published
- Change the origin address after fixing leaks, not before
07Troubleshooting after the lock-down
Locking an origin down is the moment many setups break for the first time. These are the usual symptoms and what they mean.
| Symptom | Likely cause | Fix |
|---|---|---|
| The proxy returns a 502 Bad Gateway on every page | The firewall drops the proxy itself: a missing range, the wrong port, or IPv6 left out | Load the complete published list for both address families and recheck the ports |
| Some requests time out while others work | Only part of the provider's range list is allowed | Compare your set with the current list and automate updates |
| Certificate renewal fails | HTTP-01 validation for a name that is not proxied, or whose challenge path the proxy does not forward, now meets the firewall | Switch to DNS-01 validation |
| Logs show only the proxy's addresses | Expected: every connection now comes from the proxy | Restore the client address from the X-Forwarded-For header, trusting it only from the proxy's ranges |
| The new address is found again within weeks | A leak was never fixed: SPF, mail, an AAAA record or outbound requests | Repeat the audit before renumbering again |
08For non-HTTP services
A reverse proxy only covers HTTP. Game servers, mail hosts and VPN endpoints need the same protection through routing instead: traffic reaches the provider's addresses, is filtered there, and is delivered to your machine through a GRE tunnel.
The result is the same: the address the world sees belongs to the provider, and your machine's own address is never published as the service address. The alternative is DDoS-protected hosting, where the server sits inside the filtered network and there is no separate origin to hide.
Questions
Frequently asked questions
Sources
Sources and verification
nftables ruleset syntax-checked with nft -c and loaded in an isolated network namespace (nft 1.0.6); nginx catch-all servers validated with nginx -t (nginx 1.22.1); dig loop and curl options run on Debian 12, October 2026.
Last updated