·11 min read·Updated Sep 9, 2026

Hiding Your VPS Origin IP Behind a CDN: Every Leak Path and How to Close It

How origin server IPs leak past Cloudflare and other proxies — DNS history, CT logs, mail, scanners — plus firewall, nginx and mTLS fixes that actually work.

Putting a site behind Cloudflare, BunnyCDN or any reverse proxy hides your VPS IP from casual visitors, and that's about all it does by default. The origin address is still reachable, still scannable, and still recorded in half a dozen public datasets. If your threat model includes targeted DDoS, aggressive scraping, or people who want to bypass your WAF and hit the application directly, "orange cloud on, done" is not a configuration — it's a hope.

This article walks through the realistic ways an origin IP leaks and the concrete steps that close each one, in the order that actually matters.

Direct answer

Hiding an origin IP requires two things working together:

  1. The origin must refuse traffic that doesn't come from the proxy. Enforced at the firewall (packet filter allowlist of proxy ranges) and again at the web server (default vhost that rejects unknown SNI/Host, plus mutual TLS from the proxy).
  2. No public record may point at the origin. That means no leftover A/AAAA records, no unproxied subdomains, no mail leaving the origin, no clean-DNS history, and no scannable fingerprint that ties the IP to your domain.

If either half is missing, the other doesn't save you. An allowlisted firewall on an IP that's already published in a DNS-history database still eats targeted abuse at the network edge, and a perfectly hidden IP that answers curl -H "Host: example.com" https://IP/ is not hidden at all — one scan pass finds it.

What origin hiding does and doesn't protect

Be clear about the goal. Hiding an origin IP protects against:

  • Volumetric and application-layer DDoS aimed straight at your server
  • Scrapers and bots skipping your rate limits and WAF at the proxy
  • Trivial reconnaissance that maps a domain to a machine and then port-scans it

It does not protect against:

  • Your hosting provider, who obviously knows which IP is yours
  • The CDN, which terminates TLS and sees plaintext requests and headers
  • Legal process served on the proxy or the host
  • Application vulnerabilities — a hidden IP is not a patch
  • Anyone who already correlated the IP through data you published years ago

Origin hiding is an availability and attack-surface control with a privacy side effect. It is not anonymity. If you need the network path itself to be private rather than merely fronted, that's a different tool — see the trade-offs in running a Tor onion service on a VPS, where there is no public IP to leak in the first place.

The leak paths, in order of how often they bite

1. The origin answers requests sent directly to its IP

This is the default state of every stock nginx, Apache and Caddy install. An attacker who guesses or scans your IP sends Host: yourdomain.com, gets your real site back, and confirms the match. Worse, TLS often confirms it before HTTP does: if your origin presents a certificate whose SAN list contains your domain, a full-IPv4 TLS scan (the kind Censys and Shodan run continuously) has already indexed the link.

2. Historical DNS records

Passive DNS databases keep years of A and AAAA records. If your domain ever resolved directly to the VPS — during setup, during a Let's Encrypt HTTP-01 challenge, during a five-minute "let me test before enabling the proxy" window — that IP is archived and searchable. Rotating to a proxy later does not delete history.

The safe sequence for a new deployment: create the record already proxied (or pointing to a placeholder), never publish the origin IP, and use DNS-01 certificate validation so you never need the domain to resolve to the origin.

3. Certificate Transparency and subdomain sprawl

Every publicly trusted certificate is logged. CT logs don't contain IPs, but they enumerate your hostnames for free — including dev., staging., git., old. — and one of those is usually the record someone forgot to proxy. Wildcard certificates reduce this enumeration; careful subdomain hygiene reduces it more. The mechanics and other identity leaks around domains are covered in anonymous domain registration and private DNS.

4. Unproxied DNS records

Common offenders, in rough order of frequency:

  • MX records pointing to the same machine as the website
  • AAAA records left in place when only IPv4 was proxied
  • mail., smtp., webmail., ftp., cpanel., direct., origin., vpn., monitor.
  • TXT/SPF entries containing a bare ip4: of the origin
  • NS records if you self-host authoritative DNS on the web server

Audit every record in the zone, not just the apex.

5. Outbound connections from the origin

Anything your application fetches can reveal where it lives to the operator of the far end, and sometimes to the public. Webhook deliveries, RSS/feed fetching, image proxying, link preview generation, Gravatar lookups, remote font loading, OAuth callbacks, update checks, error-reporting SDKs, and any user-triggered URL fetch (classic SSRF territory) all originate from the real IP. If untrusted users can make your server request a URL they control, your origin is one paste away from disclosure. Route sensitive egress through a separate exit, or accept that the IP is semi-public to those endpoints.

6. Email sent from the origin

Outbound mail is the single most reliable origin leak in the wild. Received: headers, Message-ID domains, bounce paths and PTR records all point home. Trigger a password reset, read the headers, done. Send transactional mail through a relay or a dedicated mail host, never straight from the web VPS. If you do run your own mail server, keep it on separate infrastructure and understand what the PTR, SPF, DKIM and DMARC setup exposes about it.

7. Internet-wide scanning fingerprints

Even with no domain in the certificate, a distinctive origin is findable. Favicon hashes, unusual HTTP headers, custom error pages, a specific server-version string, an exposed /metrics endpoint, a self-signed cert with a recognisable CN, an open Redis or Elasticsearch port — any of these lets someone search a scan dataset for "servers that look like this site" and shortlist candidates. Minimise the number of listening services and make the ones that remain boring.

8. Application-level leaks

Redirects to https://1.2.3.4/..., absolute URLs built from $_SERVER['SERVER_ADDR'], X-Powered-By and framework debug pages, phpinfo(), Git repos served under /.git/, sitemap or canonical URLs generated with the wrong base, and CSP report endpoints. Also: mixed-content resources served from a second hostname you forgot to proxy.

Locking the origin down: firewall first

Everything above is reconnaissance. The control that actually holds is a packet filter that drops HTTP/HTTPS from anyone except your proxy's published ranges.

With nftables, use named sets so you can update addresses without rewriting rules:

table inet filter {
  set proxy_v4 {
    type ipv4_addr
    flags interval
  }

  set proxy_v6 {
    type ipv6_addr
    flags interval
  }

  chain input {
    type filter hook input priority filter; policy drop;

    ct state established,related accept
    iif lo accept

    # SSH from your own management network / VPN only
    ip saddr 203.0.113.0/24 tcp dport 22 accept

    # Web traffic only from the CDN
    tcp dport { 80, 443 } ip  saddr @proxy_v4 accept
    tcp dport { 80, 443 } ip6 saddr @proxy_v6 accept

    # Keep ICMP sane, drop the rest silently
    ip protocol icmp icmp type { echo-request, destination-unreachable, time-exceeded } accept
    ip6 nexthdr icmpv6 accept
  }
}

Populate the sets from the provider's official list on a schedule. For Cloudflare:

#!/bin/sh
set -eu
V4=$(curl -fsS https://www.cloudflare.com/ips-v4)
V6=$(curl -fsS https://www.cloudflare.com/ips-v6)

nft flush set inet filter proxy_v4
nft flush set inet filter proxy_v6
nft add element inet filter proxy_v4 "{ $(echo "$V4" | paste -sd, -) }"
nft add element inet filter proxy_v6 "{ $(echo "$V6" | paste -sd, -) }"

Run it from a systemd timer, and fail closed: if the fetch errors, keep the existing set rather than flushing to empty. Two details bite people here — Docker publishing ports straight into the nat table and bypassing your input chain, and losing SSH access after a rule reload. Both are covered in the nftables baseline ruleset guide, including recovery if you lock yourself out.

Make the web server refuse strangers

Defence in depth, because IP allowlists drift and proxy ranges get shared with other customers.

Give nginx a default server that rejects unknown SNI outright, so a scanner doesn't even learn which certificate you hold:

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    return 444;
}

server {
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;
    ssl_reject_handshake on;   # nginx >= 1.19.4
}

Then require the proxy to authenticate itself with a client certificate. Cloudflare calls this Authenticated Origin Pulls; most CDNs have an equivalent:

server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate     /etc/ssl/origin.pem;
    ssl_certificate_key /etc/ssl/origin.key;

    ssl_client_certificate /etc/ssl/origin-pull-ca.pem;
    ssl_verify_client on;
    # ...
}

Two more points worth getting right:

  • Use an origin certificate, not a publicly trusted one with your domain in it. A CDN-issued origin cert or your own private CA keeps your hostname out of scan datasets and out of CT logs for that machine. Only do this if the proxy is configured to trust that issuer.
  • Restore the real client IP with set_real_ip_from for each proxy range plus real_ip_header CF-Connecting-IP. Otherwise every log line and rate limit sees the proxy, and your fail2ban rules become useless.

Verify your own exposure

Test like an attacker, from a machine that isn't the server:

# Does the origin serve the site directly?
curl -sk --resolve example.com:443:203.0.113.10 https://example.com/ -o /dev/null -w '%{http_code}\n'

# What certificate does the raw IP present?
openssl s_client -connect 203.0.113.10:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -ext subjectAltName

# Any unproxied records left?
for h in "" www. mail. smtp. dev. staging. git. vpn. cpanel. direct. origin.; do
  echo "$h example.com: $(dig +short "$h"example.com A) $(dig +short "$h"example.com AAAA)"
done

dig +short MX example.com
dig +short TXT example.com | grep -o 'ip4:[0-9.]*'

A 000 (connection dropped) or 444 from the first command is what you want. A 200 means the firewall isn't enforcing anything. Then check whether the IP already appears in passive DNS and scan datasets — search your IP and domain on Shodan, Censys, crt.sh for hostnames, and any DNS-history service. If your IP is already indexed against your domain, no amount of configuration un-publishes it.

When to rotate the IP

Rotate if the origin was ever publicly resolvable for your domain, if it appears in a scan dataset tied to your hostname, or after abuse that clearly targeted the IP rather than the domain. Rotation only helps if you fix the leak first — otherwise you burn a new address within days. Sequence: provision the new server, lock it down and test from outside, cut over at the proxy (no public DNS change to the origin at any point), then decommission the old machine. A general migration checklist applies here too, minus the DNS TTL dance, since public records never point at the origin.

Common mistakes

  • Allowlisting the proxy in the app but not the kernel. Non-HTTP services on the box are still exposed; the whole port range still answers scans.
  • Forgetting IPv6. Proxying IPv4 while an AAAA record points straight at the origin is a complete bypass.
  • Using HTTP-01 challenges after going behind a proxy. It works through most CDNs, but any fallback that requires the domain to resolve to the origin republishes the IP. DNS-01 avoids the question.
  • Leaving a staging vhost on the same machine. staging.example.com unproxied, same IP, done.
  • Trusting X-Forwarded-For without set_real_ip_from. Header spoofing becomes trivial if the origin is ever reachable directly.
  • Assuming the proxy is a shield for a vulnerable app. It filters some noise; it does not fix your code.

Trade-offs to accept

A CDN in front of your origin means a third party terminates TLS and can read every request. That is a real privacy cost, and for some threat models it's worse than the DDoS exposure you're mitigating. Alternatives: run the proxy yourself on a cheap disposable VPS (you keep control of TLS but inherit the job of absorbing attacks), publish an onion service alongside the clearnet site, or skip the proxy and rely on provider-level filtering plus tight firewalling.

There's also an operational cost. Every proxy range update, every certificate renewal, and every new subdomain is a chance to reopen a leak. Document the setup and re-run the verification commands after any DNS or infrastructure change.

FAQ

Does hiding the origin IP make my hosting anonymous? No. Your provider knows the IP is yours regardless of what the public sees. Payment and account hygiene are a separate problem from network topology.

Can I allowlist the proxy with iptables instead of nftables? Yes, the logic is identical — an ipset of proxy ranges accepted on 80/443 with a default DROP policy. nftables just handles named sets and dual-stack rules more cleanly.

What if my CDN's shared IPs mean other customers can reach my origin? That's a real limitation of IP allowlisting alone, and exactly why mutual TLS (authenticated origin pulls) matters. With ssl_verify_client on, another tenant of the same CDN can reach your port but can't get past the handshake without the issuer's client cert.

Do I still need rate limiting on the origin? Yes. The proxy can misclassify traffic, and cache-bypassing requests still land on you. Keep origin-side limits as a backstop, keyed on the real client IP.

Takeaway

Origin hiding is a checklist, not a toggle. Enforce a proxy-only allowlist in the packet filter, reject unknown SNI and require client certificates at the web server, purge every DNS record and outbound path that points home, and keep mail off the machine entirely. Then verify from the outside — because the only meaningful test is whether a stranger with your IP can still get your site.

If you're building this from scratch, provisioning a fresh VPS that has never had your domain pointed at it is the cheapest way to start clean. IronBalkans deploys full-root KVM instances in Romania in under a minute, paid with Monero, B

Written by IronBalkans. Last reviewed Sep 9, 2026.