Website Configuration

How to hide your VPS ip correctly

6 days ago
Website Configuration
7 min read

Cloudflare IP Firewall Setup Guide (Ubuntu/Debian)


1. Why This Is Important


A common mistake people make is thinking that if their website is behind Cloudflare (or a similar CDN/WAF provider), their backend server’s IP address is completely hidden. 

Unfortunately, this is not always true — your origin IP can be exposed through:
- DNS history records
- Email server headers
- Misconfigured APIs or services
- Direct IP leaks from logs or other sources

If an attacker finds your real server IP, they can bypass Cloudflare entirely by sending traffic directly to it. 
This means Cloudflare’s DDoS protection, WAF, and rate limiting won’t protect you.

Solution: Restrict your server so only Cloudflare’s IP ranges can connect to it on HTTP/HTTPS ports. This forces all requests through Cloudflare, where your protections are active.

Cloudflare publishes their IP lists here:

  • IPv4: https://www.cloudflare.com/ips-v4  
  • IPv6: https://www.cloudflare.com/ips-v6  


We will create a firewall that:
- Allows only Cloudflare IPs on ports `80` and `443`
- Allows SSH (port `22`)
- Blocks all other traffic


2. Prerequisites (Run These First)


Before running the script, install the required software and set up the environment:


# Update and upgrade system
sudo apt update && sudo apt upgrade -y

# Install required dependencies
sudo apt install -y curl software-properties-common iptables ipset iptables-persistent

# Install Node.js (change 20.x to another version if desired)
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs

# Enable persistent iptables so rules survive reboot
sudo systemctl enable netfilter-persistent
sudo systemctl start netfilter-persistent

# Create directory for the firewall script
sudo mkdir -p /opt/cloudflare-firewall
cd /opt/cloudflare-firewall



3. The Firewall Script


Create a file named `cf-firewall.js`:

nano cf-firewall.js


Paste the following script:

const { exec } = require('child_process');
const util = require('util');

const execPromise = util.promisify(exec);

async function get_ipset(name) {
    try {
        const { stdout, stderr } = await execPromise(`sudo ipset list ${name}`);
        if (stderr) {
            console.error(`Error: ${stderr}`);
            return;
        }
        return stdout; 
    } catch (error) {
        console.error(`Error: ${error.message}`);
    }
}

async function check_and_apply_iptables(set, set6) {
    const lines = set.split('\n');
    const lines6 = set6.split('\n');
    
    let isMembersSection = false;
    const stored_ips = [];
    lines.forEach((line) => {
        if (line.trim() === 'Members:') {
            isMembersSection = true;
        } else if (isMembersSection && line.trim() !== '') {
            stored_ips.push(line.trim());
        }
    });

    let isMembersSection6 = false;
    const stored_ips6 = [];
    lines6.forEach((line) => {
        if (line.trim() === 'Members:') {
            isMembersSection6 = true;
        } else if (isMembersSection6 && line.trim() !== '') {
            stored_ips6.push(line.trim());
        }
    });

    const cf_ipv4 = await fetch('https://www.cloudflare.com/ips-v4');
    const cf_ipv4_l = (await cf_ipv4.text()).split('\n').filter(ip => ip.trim() !== '');
    console.log(cf_ipv4_l);

    const cf_ipv6 = await fetch('https://www.cloudflare.com/ips-v6');
    const cf_ipv6_l = (await cf_ipv6.text()).split('\n').filter(ip => ip.trim() !== '');
    console.log(cf_ipv6_l);

    const stored_ips_sort = stored_ips.sort();
    const cf_ipv4_l_sort = cf_ipv4_l.sort();

    const stored_ips6_sort = stored_ips6.sort();
    const cf_ipv6_l_sort = cf_ipv6_l.sort();

    const areEqual = JSON.stringify(stored_ips_sort) === JSON.stringify(cf_ipv4_l_sort) && JSON.stringify(stored_ips6_sort) === JSON.stringify(cf_ipv6_l_sort);

    if (!areEqual) {
        await apply_iptables(cf_ipv4_l, cf_ipv6_l);
    }
}

async function apply_iptables(cf_ipv4_l, cf_ipv6_l) {
    const commands = [
        'sudo iptables -P INPUT ACCEPT',
        'sudo iptables -P FORWARD ACCEPT',
        'sudo iptables -P OUTPUT ACCEPT',
        'sudo ip6tables -P INPUT ACCEPT',
        'sudo ip6tables -P FORWARD ACCEPT',
        'sudo ip6tables -P OUTPUT ACCEPT',
        'sudo iptables -F',
        'sudo iptables -X',
        'sudo iptables -t nat -F',
        'sudo iptables -t mangle -F',
        'sudo iptables -t mangle -F PREROUTING',
        'sudo iptables -t raw -F PREROUTING',
        'sudo ipset flush cloudflare',
        'sudo ip6tables -F', 
        'sudo ip6tables -X',  
        'sudo ip6tables -t nat -F', 
        'sudo ip6tables -t mangle -F', 
        'sudo ip6tables -t mangle -F PREROUTING',
        'sudo ip6tables -t raw -F PREROUTING',
        'sudo ipset flush cloudflare6',
        'sudo ipset create cloudflare hash:net',
        'sudo ipset create cloudflare6 hash:net family inet6',
        'sudo iptables -A INPUT -i lo -j ACCEPT',
        'sudo iptables -A OUTPUT -o lo -j ACCEPT',
        'sudo ip6tables -A INPUT -i lo -j ACCEPT',
        'sudo ip6tables -A OUTPUT -o lo -j ACCEPT',
        'sudo iptables -A PREROUTING -t mangle -m conntrack --ctstate INVALID -p tcp ! --dport 22 -j DROP',
        'sudo ip6tables -A PREROUTING -t mangle -m conntrack --ctstate INVALID -p tcp ! --dport 22 -j DROP',
        'sudo iptables -A PREROUTING -t raw -p tcp -m multiport --dports 80,443 -m set ! --match-set cloudflare src -j DROP',
        'sudo ip6tables -A PREROUTING -t raw -p tcp -m multiport --dports 80,443 -m set ! --match-set cloudflare6 src -j DROP',
        'sudo iptables -A PREROUTING -m limit --limit 25/sec --limit-burst 50 -j ACCEPT',
        'sudo ip6tables -A PREROUTING -m limit --limit 25/sec --limit-burst 50 -j ACCEPT',
        'sudo iptables -N PriWall',
        'sudo ip6tables -N PriWall',
        'sudo iptables -A PriWall -p tcp --dport 22 -j ACCEPT',
        'sudo ip6tables -A PriWall -p tcp --dport 22 -j ACCEPT',
        'sudo iptables -A PriWall -m set --match-set cloudflare src -p tcp -m multiport --dports 80,443 -j ACCEPT',
        'sudo ip6tables -A PriWall -m set --match-set cloudflare6 src -p tcp -m multiport --dports 80,443 -j ACCEPT',
        'sudo iptables -A PriWall -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT',
        'sudo ip6tables -A PriWall -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT',
        'sudo iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT',
        'sudo ip6tables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT',
        'sudo iptables -A PriWall -j DROP',
        'sudo ip6tables -A PriWall -j DROP',
        'sudo iptables -A INPUT -j PriWall',
        'sudo ip6tables -A INPUT -j PriWall',
        'sudo iptables -P INPUT DROP',
        'sudo iptables -P FORWARD DROP',
        'sudo iptables -P OUTPUT ACCEPT',
        'sudo ip6tables -P INPUT DROP',
        'sudo ip6tables -P FORWARD DROP',
        'sudo ip6tables -P OUTPUT ACCEPT',
    ];

    for (const ip of cf_ipv4_l) {
        commands.push(`sudo ipset add cloudflare ${ip}`);
    }

    for (const ip of cf_ipv6_l) {
        commands.push(`sudo ipset add cloudflare6 ${ip}`);
    }

    commands.push('sudo iptables-save > /etc/iptables/rules.v4');
    commands.push('sudo ip6tables-save > /etc/iptables/rules.v6');
    commands.push('sudo ipset save > /etc/iptables/ipsets.rules');

    for (const cmd of commands) {
        console.log(`Executing: ${cmd}`);
        try {
            const { stdout, stderr } = await execPromise(cmd);
            if (stderr) console.error(`Error: ${stderr}`);
        } catch (error) {
            console.error(`Error: ${error.message}`);
        }
    }

    console.log('Firewall setup complete!');
}

async function setup_iptables() { 
    const set = await get_ipset('cloudflare');
    const set6 = await get_ipset('cloudflare6');
    if (!set || !set6) {
        const cf_ipv4 = await fetch('https://www.cloudflare.com/ips-v4');
        const cf_ipv4_l = (await cf_ipv4.text()).split('\n').filter(ip => ip.trim() !== '');
        console.log(cf_ipv4_l);

        const cf_ipv6 = await fetch('https://www.cloudflare.com/ips-v6');
        const cf_ipv6_l = (await cf_ipv6.text()).split('\n').filter(ip => ip.trim() !== '');
        console.log(cf_ipv6_l);

        await apply_iptables(cf_ipv4_l, cf_ipv6_l);
    } else {
        await check_and_apply_iptables(set, set6);
    }
}

setup_iptables();
setInterval(setup_iptables, 30 * 60 * 1000);


Save and exit (`CTRL+O`, `Enter`, `CTRL+X`).


4. Run the Script


node /opt/cloudflare-firewall/cf-firewall.js



If it runs without errors, your firewall is now blocking all non-Cloudflare HTTP/HTTPS traffic.


5. Make It Start Automatically on Boot (Optional but Recommended)


Create a `systemd` service file:

sudo nano /etc/systemd/system/cf-firewall.service


Paste:

[Unit]
Description=Cloudflare IP Firewall Script
After=network.target

[Service]
ExecStart=/usr/bin/node /opt/cloudflare-firewall/cf-firewall.js
Restart=always
User=root

[Install]
WantedBy=multi-user.target


Save and exit.

Enable and start:

sudo systemctl daemon-reload
sudo systemctl enable cf-firewall
sudo systemctl start cf-firewall


Check status:

sudo systemctl status cf-firewall


---

6. Verify Firewall Rules


List IPv4 rules:

sudo iptables -L -n


List IPv6 rules:

sudo ip6tables -L -n


Check IP sets:

sudo ipset list cloudflare
sudo ipset list cloudflare6



✅ Done! 
Your server now only accepts HTTP/HTTPS connections from Cloudflare’s IP ranges and is much harder to attack directly.

Last updated 1 week ago
Back to Articles