How to Use Cloudflare DNS with a VPS and Nginx
Put Cloudflare in front of a VPS on purpose by deciding when a hostname should be proxied, keeping origin HTTPS coherent, and making sure Nginx still sees the right client information.
How to choose DNS-only or proxied records, align Cloudflare SSL mode with your Nginx origin, and preserve real client IP handling.
Operators already comfortable with basic DNS who now need a cleaner Cloudflare edge model for websites and reverse-proxied apps.
If you mix up proxy mode, origin certificates, and Nginx trust settings, you can create redirect loops, bad IP logs, or expose the origin unintentionally.
Before you begin
- Have a working DNS record plan already. This guide assumes you are past basic record inventory.
- Know which hostnames are public websites and which ones should stay private or admin-only.
- Have a working Nginx server block for the hostname before you enable proxy mode.
- Be ready to test HTTP and HTTPS directly at the origin if you need to isolate Cloudflare from Nginx.
This is not a general DNS setup guide. It is the Cloudflare-specific layer that comes after basic record planning. The hard part here is not creating an A record.
It is choosing when to proxy, making sure the origin TLS story matches that choice, and keeping your Nginx behavior sane once Cloudflare sits in front.
Step 1: Decide which hostnames should be DNS-only and which should be proxied
The orange-cloud toggle is not just a performance switch. It changes the network path. With a proxied record, visitors hit Cloudflare first and Cloudflare connects to your origin. With DNS-only, visitors connect straight to your VPS.
Good default decisions look like this:
- Public website or public app: usually proxied.
- Mail-related names: usually DNS-only, because mail protocols are not a generic website traffic pattern.
- Private dashboards, SSH helpers, and internal tools: usually not public at all. Keep them behind VPN, localhost tunnels, or another private path instead of casually proxying them.
A clean pattern is to proxy only the hostnames that should serve public web traffic. Everything else should stay intentionally separate.
Step 2: Align Cloudflare SSL mode with real HTTPS at the origin
The Cloudflare SSL setting controls how Cloudflare talks to your VPS. If you choose a mode that expects HTTPS at the origin, Nginx must actually serve HTTPS correctly for that hostname. This is where many self-hosters create redirect loops or handshake failures.
Practical rules:
- Preferred production pattern: use a mode where Cloudflare reaches the origin over HTTPS and your Nginx certificate matches the hostname model you are serving.
- If the origin is only serving HTTP: do not pretend the edge is fully configured just because the browser reaches Cloudflare.
- If you use an origin certificate: remember it is for Cloudflare-to-origin traffic, not direct public browser trust.
The point is consistency. Edge HTTPS and origin HTTPS are separate legs of the path. Treat both legs as real.
Step 3: Configure Nginx for the Cloudflare origin path
Before you enable proxy mode, make sure Nginx already serves the expected hostname directly at the VPS. If the origin is broken, Cloudflare only adds another layer of symptoms.
A simple reverse-proxy server block still needs the usual hostname and upstream discipline:
server {
listen 80;
server_name app.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name app.example.com;
ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.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;
proxy_set_header X-Forwarded-Proto https;
}
}
Test before reload:
sudo nginx -t
sudo systemctl reload nginx
If you need to isolate the origin from Cloudflare during debugging, test the VPS directly first. That tells you whether you have an origin problem or an edge problem.
Step 4: Restore real client IP visibility in Nginx
Once a hostname is proxied, the connection reaching Nginx is no longer the browser's direct IP. If you do nothing, your logs and some access rules may reflect Cloudflare instead of the original client.
Configure trusted proxy handling deliberately:
real_ip_header CF-Connecting-IP;
# Trust only Cloudflare proxy ranges that you intentionally maintain.
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
The exact Cloudflare IP ranges change over time. Maintain them from Cloudflare's current published list instead of copying an old snippet forever.
CF-Connecting-IP headers from the open internet. Only accept them from the Cloudflare proxy ranges you intentionally trust.
Step 5: Keep private services out of the public edge
Cloudflare is good at fronting public web traffic. That does not mean every internal UI should become a public hostname. Admin dashboards, database UIs, and control-plane tools are usually better handled through:
- VPN access such as Tailscale or WireGuard
- localhost-only services with SSH port forwarding
- separate internal-only reverse proxies on private networks
Treat Cloudflare as part of the public edge, not as a substitute for private network design.
Troubleshooting common Cloudflare plus Nginx failures
The browser shows a redirect loop after proxying the hostname.
Your origin redirect behavior and Cloudflare SSL mode may disagree about whether the request is already HTTPS.
Nginx logs show the wrong client IP.
Set up trusted real-IP handling for Cloudflare and verify you are not logging only the proxy address.
The site works DNS-only but fails when proxied.
That usually points to an edge-to-origin problem such as SSL mode mismatch, origin certificate issues, firewall filtering, or origin hostname config.
A dashboard became internet-visible by accident.
Take it off the public DNS path and move it behind VPN, SSH forwarding, or another private access pattern.
You are not sure whether the problem is DNS, Cloudflare, or Nginx.
Test the origin directly first, then test through the proxied hostname. Separate the layers instead of debugging them all at once.