Hands-On: Put a Domain Behind Cloudflare
What We Are Building
We will take a domain (example.com) currently pointing straight at a VPS running Nginx, and put it behind Cloudflare:
- Switch nameservers to Cloudflare
- Recreate the DNS records and enable the proxy
- Set SSL to Full (Strict) with an Origin Certificate
- Add page rules so static assets are cached at the edge
The site stays online throughout — done in the right order, this migration has zero downtime.
Step 1 — Record What You Have
Before touching anything, snapshot the current DNS so you can verify nothing is lost:
dig example.com A +short
dig www.example.com A +short
dig example.com MX +short
dig example.com TXT +short
dig example.com NS +short
Save the output. Mail (MX), domain verification (TXT), and any subdomains all need to survive the move — missed records are how migrations break email.
Step 2 — Add the Site in Cloudflare
- In the Cloudflare dashboard, click Add a domain and enter
example.com - Choose the Free plan
- Cloudflare scans and imports your existing records — compare its list against the dig output from Step 1 and add anything it missed (the scan is good but not exhaustive, TXT records in particular)
- Cloudflare assigns you two nameservers, e.g.
ava.ns.cloudflare.comandkip.ns.cloudflare.com
Do not change anything at the registrar yet.
Step 3 — Set the Proxy Status Per Record
In the DNS tab, decide record by record whether traffic should route through Cloudflare:
| Record | Setting | Why |
|---|---|---|
A @ → server IP | Proxied | Web traffic — we want CDN, SSL, DDoS protection |
A www → server IP | Proxied | Same |
A api → server IP | Proxied | APIs benefit from DDoS protection too |
MX @ → mail host | n/a | MX records are never proxied |
A mail → server IP | DNS only | Mail protocols are not HTTP — proxying breaks them |
A ssh or vpn hosts | DNS only | Non-HTTP services must bypass the proxy |
The rule: proxy anything speaking HTTP, grey-cloud everything else. A proxied record for a mail or SSH host is the classic day-one Cloudflare outage.
Step 4 — Prepare SSL Before Switching
This ordering is the key to zero downtime. Set the SSL mode before the nameserver switch so there is never a redirect loop or cert mismatch when traffic starts flowing through Cloudflare.
- Go to SSL/TLS → Overview and set the mode to Full (Strict)
- Go to SSL/TLS → Origin Server → Create Certificate — accept the defaults (RSA,
example.comand*.example.com, 15 years) - Cloudflare shows a certificate and private key. Install them on your server:
sudo mkdir -p /etc/ssl/cloudflare
sudo nano /etc/ssl/cloudflare/cert.pem # paste the Origin Certificate
sudo nano /etc/ssl/cloudflare/key.pem # paste the Private Key
sudo chmod 600 /etc/ssl/cloudflare/key.pem
- Point Nginx at it:
server {
listen 443 ssl;
server_name example.com www.example.com;
ssl_certificate /etc/ssl/cloudflare/cert.pem;
ssl_certificate_key /etc/ssl/cloudflare/key.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
sudo nginx -t && sudo systemctl reload nginx
The Origin Certificate is only trusted by Cloudflare, not by browsers — that is fine, because after the switch browsers only ever talk to Cloudflare. If you already have a valid Certbot cert on the server, you can keep using it instead; Full (Strict) accepts either.
Step 5 — Switch the Nameservers
At your registrar (Namecheap, GoDaddy, Route 53 Registrar, etc.), replace the existing nameservers with the two Cloudflare assigned. Then wait. Propagation is usually under an hour, occasionally up to 24.
dig example.com NS +short
# ava.ns.cloudflare.com.
# kip.ns.cloudflare.com.
Cloudflare emails you and the dashboard status flips to Active when it detects the change. During propagation some resolvers still hit the old nameservers — harmless, since both answer with your working setup.
Step 6 — Verify the Proxy Is Live
curl -sI https://example.com | grep -iE 'server|cf-ray|cf-cache'
# server: cloudflare
# cf-ray: 8a1b2c3d4e5f6789-FRA
server: cloudflare plus a cf-ray header confirms traffic is flowing through the edge. Also confirm your origin IP is now hidden:
dig example.com A +short
# returns Cloudflare IPs like 104.21.x.x, not your server IP
While you are in the dashboard, enable two settings under SSL/TLS → Edge Certificates: Always Use HTTPS and Automatic HTTPS Rewrites.
Step 7 — Add Page Rules for Caching
By default Cloudflare caches static file extensions but passes HTML and API responses through. Tune this with page rules under Rules → Page Rules (3 free). Order matters — first match wins — so put the most specific rule first:
Rule 1 — never cache the API:
URL: example.com/api/*
Setting: Cache Level = Bypass
Rule 2 — aggressively cache the static assets directory:
URL: example.com/static/*
Settings: Cache Level = Cache Everything
Edge Cache TTL = 1 month
Browser Cache TTL = 1 day
A month-long edge TTL is safe when your build pipeline emits hashed filenames (main.a1b2c3.js) — new deploys produce new URLs, so stale cache is impossible.
Verify caching works by requesting a static asset twice:
curl -sI https://example.com/static/main.css | grep -i cf-cache-status
# first request: cf-cache-status: MISS
# second request: cf-cache-status: HIT
HIT means Cloudflare served it from the edge — your server never saw the second request.
Step 8 — Purge Cache After Deploys
If you do cache anything without hashed filenames, purge on deploy via the API:
curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/purge_cache" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{"purge_everything":true}'
The zone ID is on the domain's Overview page; create the token under My Profile → API Tokens with the Cache Purge permission only. This curl drops straight into a CI deploy job.
Common Problems
Redirect loop (ERR_TOO_MANY_REDIRECTS). SSL mode is Flexible while your server redirects HTTP to HTTPS: Cloudflare talks plain HTTP to the origin, the origin redirects to HTTPS, forever. Set Full (Strict) — which is why we did it in Step 4, before any traffic arrived.
Email stopped working. An MX target or mail host record got proxied. Set the mail-related A record to DNS only.
Real visitor IPs show as Cloudflare IPs in logs. Expected — the proxy terminates the connection. Restore real IPs in Nginx with the real_ip module reading the CF-Connecting-IP header.
Changes not visible after deploy. Edge cache is serving the old version. Purge the cache, or use hashed asset filenames and cache only those.