All systems operational 6 offshore regions No-KYC checkout
Hands-on Field guide

Hide your origin IP: an offshore reverse proxy that holds

Everyone repeats the same advice — put a CDN in front of it and the real server disappears. It does not. The origin address survives in passive-DNS archives, in certificate transparency logs, in the headers of your own outgoing mail, and in every request your application makes to the outside world. This guide builds the version that actually holds: an edge node from $8.00/mo in a different region, a WireGuard tunnel, an origin with no public listener at all — and the searches we run afterwards to try to find our own boxes.

Updated 2026-09-02 · 15 min read · Fleet operations
On this page
  1. What hiding the origin actually buys you
  2. Every way an origin IP leaks
  3. Two shapes of edge: a CDN, or a box you own
  4. The tunnel is the part people get wrong
  5. Certificates, and the log that publishes your hostnames
  6. Mail, and the other services answering on the wrong address
  7. Egress: the connections your origin starts
  8. What it costs, and how to prove it works
  9. Step by step
SP·01

What hiding the origin actually buys you

Three things, and they are worth being precise about. First, the flood stops arriving at the machine that cannot absorb it: a single VPS has a finite uplink, and when the address that eats packets is a purpose-built edge instead of the box holding your database, a volumetric attack becomes somebody else's engineering problem. Second, the application stops being reachable around its own defences — rate limits, bot rules, a WAF and geo-blocking are all trivially bypassed by anyone who can connect straight to the origin, and most people who deploy them never check whether that is still possible. Third, the address serving your content stops being the address holding your data, which is the separation that makes an abuse complaint, a scan, or a targeted probe land somewhere cheap and replaceable.

And now the honest half. Hiding an origin is not anonymity — it hides an address, not a person, and the payment trail, the domain registration and the account behind them are a separate problem that has its own guide. It does not patch your application: an origin nobody can find is still exploitable the moment somebody finds it, and origins do get found. It does not hide anything from your provider, who by definition knows which machine answers on which address. Treat it as one layer that raises the cost of an attack, sitting on top of a box that was properly hardened first — not as a substitute for either.

SP·02

Every way an origin IP leaks

The reason so many hidden origins are not hidden is that people close one channel and assume the rest followed. They do not. Here is the list we work through, in rough order of how often it is the one that actually burned somebody:

  • Historical DNS. Passive-DNS collectors have been recording your A record since long before you moved behind a proxy. The address you used last year is a permanent, searchable, free lookup.
  • Certificate transparency. Every publicly trusted certificate is published to append-only public logs, with every hostname it covers. A cert issued for origin.example.com announces that name to the world, and its A record does the rest.
  • Subdomains that never moved. mail, ftp, webmail, cpanel, dev, staging, vpn, monitor — the apex went behind the CDN and these stayed pointed at the box.
  • Mail. An MX record on the origin gives the address away directly; so does a Received: header in a mail your application sent, which anybody can trigger with a password-reset form.
  • Outbound requests. Webhooks, avatar fetches, RSS pulls, link previews, update checks, OAuth callbacks. Each one reveals the origin's address to whoever operates the far end — and a URL-preview feature lets an attacker choose that far end.
  • Answering on the bare address. If the origin still serves your site to a request with no matching Host header, internet-wide scanners have already indexed it: favicon hash, page title, certificate fingerprint and HTTP header order are all searchable.
  • The IPv6 record you forgot. The A record moved to the proxy; the AAAA record still points home.
  • The application talking about itself. Absolute URLs in a CMS config, redirects to an internal hostname, stack traces, Server banners, source maps, an unauthenticated status endpoint.

Note what most of these have in common: they are permanent. Certificate logs are append-only and passive DNS is an archive. You cannot retract an address once it is published — you can only stop using it, which is exactly why the order of operations below matters.

SP·03

Two shapes of edge: a CDN, or a box you own

A commercial CDN gives you anycast capacity no single server can match, in dozens of cities, often on a free tier. The trade is that TLS terminates on infrastructure you do not control: the operator sees your plaintext, knows which account it belongs to, and can be compelled to act on that knowledge or simply decide one morning that your content is not welcome. There is also a subtler problem specific to shared fronts — if your origin firewall allows the CDN's published address ranges, then anyone else with an account on that CDN is inside your allowlist and can point their own hostname at your origin. That is a real bypass, not a theoretical one, and it is why authenticated origin pull exists.

An edge you run yourself is the opposite trade. Nobody but you holds the private key, the box sits in a jurisdiction you chose deliberately, and it costs $8.00/mo for the smallest plan — genuinely a rounding error next to what it protects. What you do not get is anycast: a 200 Gbps flood will saturate the edge's uplink no matter how elegant your nginx config, so network-level absorption has to come from somewhere. In our case that is the 1.5 Tbps of upstream mitigation in front of every machine in the fleet, which is what makes a self-run edge viable rather than a single point of collapse. The two shapes also compose: CDN in front for reach and volume, your own node behind it for the part you refuse to hand over. Pick based on which failure you would rather explain.

SP·04

The tunnel is the part people get wrong

The common setup is an origin that listens on 0.0.0.0:443 with a firewall allowlisting the proxy's addresses. It works, and it is the weakest link in the design. Allowlists drift — published ranges change and the update never gets applied; they are shared, so on a public CDN they admit every other customer; and they fail open in the direction that matters, because the origin is still a live public listener the whole time, waiting for one misconfiguration or one ufw disable during a debugging session.

The version that holds inverts it: the origin has no public listener at all. A WireGuard tunnel is established between edge and origin, the web server binds to the tunnel address only, and the public interface has a default-deny policy on both IP families with no exception for 80 or 443. Then reachability is not a rule anyone can forget to renew — it is the absence of a route. WireGuard is the right tool here because it is a kernel module with a tiny attack surface, it is silent to unauthenticated scanners (an unauthenticated packet gets no reply at all, so the UDP port does not even appear to exist), and it costs single-digit microseconds per packet. If you have not set one up before, the WireGuard guide covers the fundamentals; here we only need a two-peer point-to-point link.

One rule before you start, and it is the one that saves your afternoon: keep a second SSH session open the whole time. Locking SSH behind a tunnel you are still reconfiguring is how people lose a machine, and on a host with no identity on file there is no support ladder to climb and no console session anyone can grant you — the way back in is a redeploy and a restore, which is quick if your backups are current and final if they are not.

SP·05

Certificates, and the log that publishes your hostnames

Certificate transparency is a genuinely good thing that will happily undo your week. Every certificate a public CA issues is submitted to append-only logs anyone can search, and the entry contains every name in the certificate. Issue one for origin.example.com or direct.example.com and you have published, permanently and in a structured format, the exact hostname you were trying not to advertise. Worse, the habit of putting staging and admin hostnames into the same SAN list turns one careless renewal into a map of your infrastructure.

The discipline is simple. Public certificates live on the edge only, covering only the names the public actually uses. The origin gets a self-signed certificate or one from a small private CA, pinned in the proxy configuration with proxy_ssl_trusted_certificate — nothing on a link that only your own proxy speaks needs to be publicly trusted, and issuing a public cert for it buys you nothing but an entry in a log. If you need many public subdomains, a wildcard publishes one name instead of thirty. And switch ACME to the DNS-01 challenge: HTTP-01 requires something to answer on port 80 for the hostname being validated, which is precisely the public listener you just removed. Finally, accept the asymmetry — logs are append-only, so a hostname you already published cannot be withdrawn. If it resolved to the origin, the origin needs a new address.

SP·06

Mail, and the other services answering on the wrong address

Mail is the classic bypass because it has to be reachable by definition. If the MX record for your domain points at the origin, the exercise is over before it started — the record is public and one dig ends the hunt. Even with MX elsewhere, an application that sends mail directly from the origin stamps the sending host's address into the Received: chain of every message, and any form that mails a user on demand turns that into a self-service lookup. The fix is to make the origin a client and never a server: relay outbound mail through a submission service or a separate box, keep MX on a machine that is allowed to be found, and read the full headers of a test message before you call it done. Running your own mail at scale is a project in itself, and it does not belong on the machine you are hiding.

Then do a sweep for everything else that is quietly listening. Monitoring agents, container dashboards, database ports opened "temporarily", a metrics endpoint on 9100, a control panel on a high port, an SSH daemon on the public interface. Every one of them is a service that answers on the address you are trying to keep private, and scanners find high ports as easily as low ones. The audit is one command — ss -tulpn — and the correct output is a list where nothing is bound to a public address. Step three below is what makes that true and keeps it true.

SP·07

Egress: the connections your origin starts

An origin that accepts nothing can still betray itself, because it does not only receive connections — it opens them. Package mirrors, NTP, a webhook to a payment processor, a bot API, an image fetched for a link preview, a licence check, an outbound SMTP session, a Git remote, an error reporter. To the far end of each of those, the origin's public address is simply the source address of the connection. Most of the time this is harmless, because you chose the far end and you trust it. The problem is the handful of endpoints an attacker chooses: paste a link into anything that renders a preview, register a webhook, or find a server-side request forgery in an image importer, and the origin resolves and connects to a host the attacker is watching. That is a two-minute deanonymisation with no exploit involved.

There are two defensible answers. The strict one routes all egress over the tunnel and lets the edge NAT it, so the origin's outbound source address is the edge's — one setting in the peer config, plus forwarding and a masquerade rule at the other end. wg-quick handles the routing loop for you: with a 0.0.0.0/0 route it installs an fwmark rule so the tunnel's own packets still reach the endpoint directly, which is the part people break when they write the routes by hand. The pragmatic answer keeps direct egress for traffic you control and puts a proxy in front of anything that fetches a user-supplied URL. What is not defensible is not knowing which of the two you have. Decide it deliberately, then verify it with a request to a host you own and a look at the source address in its logs.

SP·08

What it costs, and how to prove it works

The budget line is one extra VPS. The smallest plan at $8.00/mo terminates TLS and proxies for a small site without noticing the load — a reverse proxy is mostly a socket copy, and 2 vCPU with 4 GB of RAM is comfortable well past the point where the origin behind it becomes the bottleneck. Put it in a different region from the origin so a single legal instrument or a single facility problem does not reach both, and remember the latency: an extra hop adds real milliseconds, so an edge in Amsterdam in front of an origin in Kuala Lumpur is a design decision, not an accident. Pairs inside the same continent typically cost single-digit milliseconds, and the TLS session reuse you gain at the edge often pays that back on a real page load.

Proving it works is the part that separates a configuration from a control, and it is a recurring job rather than a one-off — every new subdomain, every new certificate, every new integration is a fresh chance to republish the address. The battery in step seven takes about ten minutes: try to reach your site directly on the origin address, list every hostname you ever certified, walk the obvious subdomain names, check the AAAA record you forgot, and send yourself a mail. Run it after every infrastructure change. And keep the conclusion in mind while you do: if the origin answers, the correct fix is not another firewall rule — it is a new address, because the old one is already in somebody's archive. In the stack, this layer sits after hardening and alongside off-site backups: hardening decides how hard the box is to break, backups decide how fast you recover, and this decides how hard the box is to find in the first place.

SP·09

Step by step

  1. 01

    Deploy the edge and give it exactly one job

    Deploy a second VPS in a region that is not where the origin lives, and treat it as a single-purpose appliance: TLS termination, a reverse proxy, and nothing else. No database, no application code, no shell scripts anyone would miss. Run the first-hour checklist on it — key-only SSH, default-deny firewall on both IP families, unattended security updates — then open exactly three ports.

    ssh root@198.51.100.20
    apt update && apt full-upgrade -y
    hostnamectl set-hostname edge-01
    apt install -y nginx wireguard-tools ufw unattended-upgrades
    
    ufw default deny incoming && ufw default allow outgoing
    ufw limit 22/tcp
    ufw allow 80,443/tcp
    ufw allow 51820/udp
    ufw --force enable
  2. 02

    Bring the tunnel up before you touch DNS

    Two peers, one link. Generate a keypair on each machine and give the tunnel its own small subnet — the origin will end up reachable at 10.66.0.2 and nowhere else. The origin dials out to the edge (it is the side that will have no open ports), so it carries the Endpoint and a keepalive; the edge only listens.

    # on both machines
    umask 077; wg genkey | tee privkey | wg pubkey > pubkey
    
    # edge-01 — /etc/wireguard/wg0.conf
    [Interface]
    Address = 10.66.0.1/24
    ListenPort = 51820
    PrivateKey = <edge-privkey>
    
    [Peer]
    PublicKey = <origin-pubkey>
    AllowedIPs = 10.66.0.2/32
    
    # origin-01 — /etc/wireguard/wg0.conf
    [Interface]
    Address = 10.66.0.2/24
    PrivateKey = <origin-privkey>
    
    [Peer]
    PublicKey = <edge-pubkey>
    Endpoint = 198.51.100.20:51820
    AllowedIPs = 10.66.0.1/32
    PersistentKeepalive = 25

    Enable it at both ends and confirm the handshake before going any further — a tunnel that only works until the next reboot is worse than none.

    systemctl enable --now wg-quick@wg0
    wg show          # expect a recent handshake and non-zero transfer
    ping -c3 10.66.0.1   # from the origin
  3. 03

    Make the origin unreachable from the public internet

    This is the step that does the actual work, and the one where people lock themselves out. Open a second SSH session and leave it connected before you run anything below — there is no support console to fall back on here. Then bind the web server to the tunnel address, drop everything on the public interface, and allow only the tunnel plus the WireGuard endpoint itself.

    # /etc/nginx/sites-available/app  — listen on the tunnel only
    listen 10.66.0.2:8080;
    
    ufw --force reset
    ufw default deny incoming
    ufw default allow outgoing
    ufw allow in on wg0 to any port 8080 proto tcp
    ufw allow in on wg0 to any port 22 proto tcp
    ufw allow from 198.51.100.20 to any port 51820 proto udp
    ufw --force enable
    
    # the audit: nothing may be bound to a public address
    ss -tulpn | grep -Ev '10\.66\.0\.2|127\.0\.0|\[::1\]'

    If that last command prints a service, it is a leak — fix the bind address rather than adding a firewall rule around it. Two listeners are allowed to survive: WireGuard on 51820, and sshd if you have not yet moved it onto the tunnel.

  4. 04

    Terminate TLS at the edge and proxy over the tunnel

    Issue the public certificate on the edge, for the names the public actually uses, and proxy upstream to the tunnel address. The second server block is not optional decoration: it is what stops the edge from serving your site to a scanner that connects by IP with no Host header, which is how the front door gets fingerprinted.

    # edge-01 — /etc/nginx/sites-available/example.com
    server {
        listen 443 ssl;
        http2 on;
        server_name example.com www.example.com;
    
        ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
        add_header Strict-Transport-Security "max-age=63072000" always;
    
        location / {
            proxy_pass http://10.66.0.2:8080;
            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_http_version 1.1;
        }
    }
    
    # anything that is not a known hostname gets nothing at all
    server {
        listen 80 default_server;
        listen 443 ssl default_server;
        ssl_reject_handshake on;
        return 444;
    }
  5. 05

    Give the origin back the real client IP

    Behind a proxy, every request arrives from 10.66.0.1. Left alone, your access logs become useless, per-IP rate limiting throttles the tunnel instead of the attacker, and fail2ban eventually bans the edge and takes the site down — a genuinely popular way to cause an outage while hardening. Trust the forwarded header, but only from the tunnel address, never from the world.

    # origin-01 — /etc/nginx/conf.d/realip.conf
    set_real_ip_from 10.66.0.1;
    real_ip_header   X-Forwarded-For;
    real_ip_recursive off;

    Do the equivalent in the application — ProxyFix in Flask, TRUSTED_PROXIES in Laravel, set_real_ip_from plus the framework's own trusted-proxy list — and put the rate limiting on the edge, where the real client address exists natively:

    # edge-01 — /etc/nginx/nginx.conf (http block)
    limit_req_zone $binary_remote_addr zone=front:10m rate=20r/s;
    # then, inside the location block
    limit_req zone=front burst=40 nodelay;
  6. 06

    Move mail and egress off the origin address

    Point MX at a machine that is allowed to be found, send outbound mail through a relay rather than straight from the origin, and switch certificate renewal to the DNS-01 challenge so nothing has to answer on port 80. Then decide what happens to the rest of the outbound traffic. To route all of it through the edge, widen the origin's AllowedIPs and let the edge masquerade — wg-quick installs the fwmark rule that keeps the tunnel itself reachable, so you do not need to hand-write a route for the endpoint.

    # origin-01 — /etc/wireguard/wg0.conf, in [Peer]
    AllowedIPs = 0.0.0.0/0, ::/0
    
    # edge-01 — forward and NAT the tunnel
    echo 'net.ipv4.ip_forward=1' > /etc/sysctl.d/99-fwd.conf && sysctl --system
    ufw route allow in on wg0 out on eth0
    # in /etc/ufw/before.rules, above the *filter block:
    # *nat
    # :POSTROUTING ACCEPT [0:0]
    # -A POSTROUTING -s 10.66.0.0/24 -o eth0 -j MASQUERADE
    # COMMIT
    systemctl restart ufw && wg-quick down wg0 && wg-quick up wg0

    Then check it from the origin: curl -s https://ifconfig.co must return the edge's address, not its own.

  7. 07

    Hunt for your own origin, then write the runbook

    Attack it the way somebody else would. The first command is the important one — if the origin still serves your site when addressed directly, nothing above is working yet.

    # does the origin answer for your hostname?
    curl -sk --resolve example.com:443:203.0.113.10 https://example.com/ \
         -o /dev/null -w '%{http_code}\n'      # want: a timeout, not 200
    
    # every hostname you have ever certified, from the public logs
    curl -s 'https://crt.sh/?q=%25.example.com&output=json' \
      | grep -o '"name_value":"[^"]*"' | sort -u
    
    # records that never moved
    dig +short example.com A; dig +short example.com AAAA; dig +short example.com MX
    for h in www mail ftp webmail cpanel dev staging vpn monitor origin direct; do
      printf '%-9s %s\n' "$h" "$(dig +short $h.example.com | tr '\n' ' ')"
    done

    Then send yourself a mail from the application and read the full Received: chain, and paste a link to a host you control into any feature that renders previews and check which address fetched it. Write down what a correct result looks like for each check, and rerun the lot after every DNS change, every new certificate and every new integration. If any of them turns up the origin, rebuild it on a fresh address — the published one is already archived, and no firewall rule will take it back.

SP·10 — FAQ

Quick answers

Isn't a CDN enough on its own?

Only if the origin genuinely cannot be reached without it, and by default it can. A CDN changes where your DNS points; it does not remove the origin's public listener, retract the address from passive-DNS archives, move your MX record, or stop your application from making outbound requests. The usual result is a site that is proxied and still trivially resolvable. A CDN is a good front — put one in front of the edge if you want its capacity — but the property you actually want is the one from step three: the origin has no route from the public internet at all, so there is nothing to bypass.

Does hiding the origin IP make me anonymous?

No, and conflating the two is how people end up with a false sense of safety. This hides an address. Your identity leaks through entirely different channels: the domain registration, the payment method, the account you signed up with, and the operational habits that link them. Hosting with us closes some of those by construction — registration is a handle and a password, funding is a prepaid crypto balance, and there is no identity document anywhere in the loop — but the domain and the payment trail are yours to manage. Keeping your name off a server covers that side properly.

How much latency does the extra hop add?

Exactly the round-trip between edge and origin, so it is a placement decision rather than a fixed tax. Two European regions are typically in the low single-digit milliseconds; Amsterdam to Kuala Lumpur is not, and you should not design that pair by accident. Some of it comes back: the edge terminates TLS close to the visitor and keeps a warm connection to the origin, so handshake-heavy first loads often improve. If you are latency-sensitive, put the edge near your visitors and the origin near the edge, and measure with real page loads rather than pings.

What happens when the edge goes down?

The site goes down, because the origin has deliberately no other way in — that is the design, not a bug. Handle it the way you would any single point of failure: run two edges in different regions with both addresses in DNS, and keep the origin's peer list holding both. The tunnel comes back on its own after a reboot if you enabled wg-quick@wg0, so the ordinary failure resolves itself. What you must never do is add a temporary public listener on the origin during an incident; it will still be there in six months, and by then the address will have been scanned and archived.

Can I do this with one server?

Not meaningfully. A reverse proxy on the same machine hides nothing — the address that answers is the address you were trying to conceal. The cheapest honest version is a second VPS at $8.00/mo doing nothing but terminating TLS, which is also the version where a flood or an abuse complaint lands on a box you can replace in 15 min without touching your data. The one genuine single-machine alternative is publishing as a Tor onion service, which needs no public address at all; it is a different product with a different audience, not a drop-in replacement for a public website.

My origin IP is already public. Is it too late?

For that address, yes — passive-DNS archives and certificate logs are permanent and there is no retraction mechanism. But the fix is cheap: build the edge and the tunnel first, deploy a new origin on a fresh address, migrate onto it, and destroy the old machine. Everything published points at an address that no longer runs anything. Do it in that order, because standing up a new origin before the tunnel exists simply publishes a second address. And take the opportunity to fix the leak that exposed the first one — otherwise you will be doing this again next year.

Put it into practice

VPS online in 15 min, dedicated handed over in 2–12 h. Top up from $30.00 in crypto — no identity attached.

Deploy a VPS