Get Rewarded! We will reward you with up to €50 credit on your account for every tutorial that you write and we publish!

How to protect a website with Cloudflare without exposing ports to the Internet

profile picture
Author
Soundarahari Parthiban
Published
2024-04-03
Time to read
10 minutes reading time

About the author- Linux sys-admin fiddling with servers

Introduction

Hello, and welcome to this tutorial on how to protect a website with Cloudflare. It is explained how to host a website on a server, how to protect it with Cloudflare's awesome and free L7 protection while staying secure, and how to lock down the server's ports so that the attacker cannot bypass Cloudflare and attack the server directly.

About Cloudflare

Before we get started, a small gist on Cloudflare and its features:

  • Cloudflare is one of the world’s largest networks. Today, businesses, non-profits, bloggers, and anyone with an Internet presence boast faster, more secure websites and apps. They are protected from DDoS attacks, and their origin servers are shielded from the attackers. Cloudflare also provides a suite of security services that protect the website from attacks, such as SQL injection, XSS, and DDoS attacks.
  • Cloudflare powers Internet requests for millions of websites and serves 55 million HTTP requests per second on average.
  • Cloudflare has made huge improvements in the security and performance of the Internet. It is a great tool for website owners to protect their websites from attacks and improve the performance of their websites. The best part is that it is free to use.
  • As your traffic goes higher, you can always upgrade to a paid plan for more features and better performance.

Prerequisites

This guide assumes that you already added your domain to Cloudflare and are pointing to the Cloudflare nameservers. If you haven't done that yet, you can follow the guide here.

If you do not have a domain yet, you can get one from any domain registrar, I would recommend Hetzner.

Before we get started, you will need the following:

  • A domain name
  • A Cloudflare account with the domain added
  • A server running Debian 12

    Other distro's should work too, but this tutorial is based on Debian 12.

  • A website to protect

Step 1 - Updating the server and installing NGINX

First and foremost, we need to update the server, then we should add the NGINX repository and install NGINX.

apt update && apt upgrade -y
apt install curl gnupg2 ca-certificates lsb-release debian-archive-keyring -y
# Add the signing key for the nginx packages
curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor | tee /usr/share/keyrings/nginx-archive-keyring.gpg > /dev/null
# Add the NGINX repository
echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/debian `lsb_release -cs` nginx" | tee /etc/apt/sources.list.d/nginx.list
# Pin down the NGINX repository to avoid conflicts with the default Debian repository
echo -e "Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n" | tee /etc/apt/preferences.d/99nginx
apt update
# Install NGINX
apt install nginx -y

You can use systemctl status nginx to check if NGINX is active and running. If not, you can try and run:
systemctl start nginx && systemctl enable nginx

Step 2 - Setting up and configuring the website

Configuring NGINX to serve the website is out of scope for this tutorial, but we will be using default configurations for this tutorial and securing the server with Cloudflare.

You will need to generate an origin certificate from Cloudflare and place it in the /etc/nginx/ssl directory, namely cf.crt and cf.key.

You can generate the origin certificate in the Cloudflare dashboard by selecting your domain and navigating to SSL/TLS » Origin Server » Create Certificate.

Cloudflare Create Certifigate

You can adapt the configuration below (e.g. /etc/nginx/sites-available/default) to your website's needs.

server {
    listen       443 ssl;
    server_name  <example.com>;
    access_log  /var/log/nginx/host.access.log  main;
    ssl_certificate /etc/nginx/ssl/cf.crt;
    ssl_certificate_key /etc/nginx/ssl/cf.key;
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

Step 2.1 - Reloading NGINX

In this step, we will reload NGINX to apply the changes and point the domain to the server.

systemctl reload nginx

After reloading NGINX, you can point the domain to the server by adding an A record in the Cloudflare dashboard. Make sure the proxy status is set to "Proxied" to enable Cloudflare's L7 protection, else the server will be exposed to the Internet.

Step 3 - 60% of the way there

We are now 60% of the way there. The website is now protected by Cloudflare's L7 protection, but the server is still exposed to the Internet, and an attacker can bypass Cloudflare and attack the server directly.

In the next steps, we will be locking down the server's ports and only allowing Cloudflare's IP ranges to access the server.

We can use iptables to achieve this, but we will be going one step further and use Hetzner's Cloud Firewall to lock down the server's ports.

Step 4 - Setting up Hetzner Cloud Firewall

In this step, we will be setting up the Hetzner Cloud Firewall to lock down the server's ports and only allow Cloudflare's IP ranges to access the server.

You could do this via the Hetzner Cloud Console, by selecting your project and clicking on the "Firewalls" tab. However, Cloudflare's IP ranges are dynamic and can change over time. For this reason, we will be using the Hetzner Cloud API to automate this process.

First, you need to do a few things in the Hetzner Cloud Console:

  • Create an API token by navigating to "Security" » "API tokens" and clicking on "Generate API token". Make sure you give it Read & Write permissions.
  • Note down the Firewall ID. You can find it in the URL when you are on the Firewall's page:
    https://console.hetzner.cloud/projects/<project-id>/firewalls/<firewall-id>/rules
  • Make sure that the firewall is applied to the server, else it's of no use.

Back on your server, save the script below in a file, e.g. firewall.py. This script will whitelist Cloudflare's IP ranges in the Hetzner Cloud Firewall.

Note: The script below will overwrite all existing rules of the firewall you specified, so make sure the firewall does not include any other important rules. If it does, you might want to add those to the script, such as opening the SSH port.

Replace YOUR_HETZNER_API_TOKEN and YOUR_HETZNER_FIREWALL_ID with your actual API token and Firewall ID.

import requests
import json

HETZNER_API_TOKEN = "YOUR_HETZNER_API_TOKEN"
HETZNER_FIREWALL_ID = "YOUR_HETZNER_FIREWALL_ID"
def get_cloudflare_ips(version):
    url = "https://www.cloudflare.com/ips-v" + version
    print("Getting IPs from " + url)
    response = requests.get(url)
    if response.status_code == 200:
        return response.text.strip().split('\n')
    else:
        raise Exception("Failed to retrieve Cloudflare IP ranges", response)

def whitelist_ips_in_hetzner(ip_ranges):
    headers = {
        'Authorization': f'Bearer {HETZNER_API_TOKEN}',
        'Content-Type': 'application/json',
    }
    payload = {
        "rules": [
            {
                "direction": "in",
                "source_ips": ip_ranges,
                "port": "443",
                "protocol": "tcp",
                "description": "Accept port 443"
            },
            #{
            #    "direction": "in",
            #    "source_ips": ["0.0.0.0/0","::/0"],
            #    "port": "22",
            #    "protocol": "tcp",
            #    "description": "Accept SSH connections"
            #}
        ]
    }

    response = requests.post(f'https://api.hetzner.cloud/v1/firewalls/{HETZNER_FIREWALL_ID}/actions/set_rules', headers=headers, data=json.dumps(payload))
    if 200 <= response.status_code < 203:
        print("IPs whitelisted successfully in Hetzner Firewall")
    else:
        print("Failed to whitelist IPs in Hetzner Firewall", response.json())

if __name__ == "__main__":
    cloudflare_ips_v4 = get_cloudflare_ips("4")
    cloudflare_ips_v6 = get_cloudflare_ips("6")
    combined_ips = cloudflare_ips_v4 + cloudflare_ips_v6
    print("Whitelisting these IPs:")
    for ip in combined_ips:
        print(ip)
    whitelist_ips_in_hetzner(combined_ips)

Step 5 - Running and automating the script

  • Install Python
    To run the script, you need to have Python installed on the server. If you don't have Python installed yet, you can install it by running:

    apt install python3 -y
  • Run the script
    Once you have Python installed, you can run the script with:

    python3 firewall.py

    You should be expecting an output like "IPs whitelisted successfully in Hetzner Firewall". If not, you can check the error message and troubleshoot.

  • Automate
    Once you have the script running successfully, you can automate it by adding it to a cron job, so that the script runs every 24 hours and updates the Hetzner Cloud Firewall with Cloudflare's IP ranges.

    You can add a cron job by running crontab -e and adding the following line to the file.

    Replace /path/to/firewall.py with the actual path to your file.

    * 0 * * * /usr/bin/python3 /path/to/firewall.py

Step 6 - Hardening the protection on Cloudflare side

Login to your Cloudflare dashboard.

  • Navigate to the "SSL/TLS" tab

    To ensure that the website is always served over HTTPS and the connection between Cloudflare and the server is encrypted and secure:

    • Enable "Always Use HTTPS"
    • Enable "Automatic HTTPS Rewrites"
    • Set the SSL/TLS encryption mode to "Full (strict)"

  • Navigate to the "Security" tab

    To ensure that the website is protected from bots, hotlinking and other attacks:

    • Enable "Browser Integrity Check"
    • Enable "Hotlink Protection"
    • Enable "Bot Fight Mode"

Note that this is not a one-size-fits-all solution, and you should always keep an eye on the Cloudflare dashboard and tweak the settings according to your website's needs.

  • You can also enable "Rate Limiting" to further protect the website from attacks, and luckily, Cloudflare provides a free tier for this feature (includes one rule).
  • You can also use Cloudflare's caching feature to cache the website's static content and reduce the server's load.

And once again, you should monitor traffic and tweak the settings according to your website's needs.

There's also this script: https://github.com/guided-hacking/cfautouam

You can use this script to automatically turn on Cloudflare "Under Attack Mode" when the server is using too much CPU or RAM. This can be useful to protect the server from attacks, but this is out of scope for this tutorial.

Conclusion

In this tutorial, you learned how to protect a website with Cloudflare's L7 protection without exposing ports to the Internet, securing the server and the website from attacks.

You also automated the process of updating the Hetzner Cloud Firewall with Cloudflare's IP ranges, and hardened the protection on Cloudflare's side.

I hope this tutorial was helpful and you were able to protect your website with Cloudflare.

Monitor the website's traffic and tweak the settings according to your website's needs, and always keep an eye on the Cloudflare dashboard for any alerts and warnings.

License: MIT
Want to contribute?

Get Rewarded: Get up to €50 in credit! Be a part of the community and contribute. Do it for the money. Do it for the bragging rights. And do it to teach others!

Report Issue
Try Hetzner Cloud

Get 20€ free credit!

Valid until: 31 December 2024 Valid for: 3 months and only for new customers
Get started
Want to contribute?

Get Rewarded: Get up to €50 credit on your account for every tutorial you write and we publish!

Find out more