Protection

6 min readUpdated

How to hide your origin IP address

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.

Audit what you publish, then test the origin directly
# 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.

Default-deny on the web ports with nftables (validated with nft -c and loaded in a netns)
# 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.

Catch-all servers that reveal nothing (validated with nginx -t)
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.

SymptomLikely causeFix
The proxy returns a 502 Bad Gateway on every pageThe firewall drops the proxy itself: a missing range, the wrong port, or IPv6 left outLoad the complete published list for both address families and recheck the ports
Some requests time out while others workOnly part of the provider's range list is allowedCompare your set with the current list and automate updates
Certificate renewal failsHTTP-01 validation for a name that is not proxied, or whose challenge path the proxy does not forward, now meets the firewallSwitch to DNS-01 validation
Logs show only the proxy's addressesExpected: every connection now comes from the proxyRestore the client address from the X-Forwarded-For header, trusting it only from the proxy's ranges
The new address is found again within weeksA leak was never fixed: SPF, mail, an AAAA record or outbound requestsRepeat 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

No, and treating it as sufficient is the common mistake. Assume it will eventually be found and limit the damage by firewalling the origin to your provider's ranges, so the address alone no longer reaches your application.

Mostly through history rather than cleverness: old DNS records, certificate transparency logs, unproxied subdomains, SPF records, mail headers, internet-wide scans and databases that archive DNS changes.

If it has already been published, yes, but fix the leak first, or the new address ends up exposed the same way.

Only for the names you proxy, and only from then on. Records published earlier, unproxied subdomains, mail and certificates can all still point to the server, which is why the firewall step matters more than the DNS change.

Yes. Apply the same allow-list to IPv6, or turn IPv6 off on the origin if you do not use it. A firewall that only covers IPv4 leaves an AAAA record as an open side door.

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.

  1. 01CloudFlair (christophetd/CloudFlair) · GitHub
  2. 02RFC 9162: Certificate Transparency Version 2.0 · IETF
  3. 03RFC 7208: Sender Policy Framework (SPF) · IETF
  4. 04Module ngx_http_ssl_module (ssl_reject_handshake) · nginx.org
  5. 05Sets · nftables wiki

Last updated