tech
How to Host Your Website on a Raspberry Pi with Cloudflare Tunnels
· updated
In this guide, we’ll walk through setting up a secure and accessible web server using a Raspberry Pi and Cloudflare Tunnels. This setup allows you to host your website from home without exposing your home IP address or opening ports on your router, thanks to Cloudflare’s secure tunneling technology.
Prerequisites
- A Raspberry Pi (We used a Raspberry Pi 4 with 8GB RAM, but other models should work with adjustments)
- Raspberry Pi OS installed on the Pi
- SSH access to your Raspberry Pi
- A domain name you own
- A Cloudflare account
- A static website in a Git repository, ready to be deployed (We used an Astro site for this example, but the steps apply to other types of sites as well)
Step 1: Prepare Your Raspberry Pi
First, ensure your Raspberry Pi is up to date and install Nginx, which will serve as our web server.
-
Update your system:
sudo apt update sudo apt full-upgrade sudo reboot -
Install Nginx:
sudo apt install nginx -
Verify Nginx Installation:
Visit your Raspberry Pi’s local IP address in a browser. You should see the default Nginx welcome page.
Step 2: Build Your Website on the Pi
Instead of building your website elsewhere and copying files to /var/www, clone your website’s repository on the Pi and build it in place. Nginx will serve the build output directly from the repository, which makes updating the site as simple as pulling and rebuilding (more on that in the last step).
-
Install Git and Node.js (via nvm):
sudo apt install git curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash nvm install --lts -
Clone your website’s repository and build it:
mkdir -p ~/dev && cd ~/dev git clone https://github.com/yourusername/mywebsite.git cd mywebsite npm install npm run buildThis creates a
distfolder (or your configured output folder) containing your website’s static files. That folder is exactly what Nginx will serve, no copying involved, so there are never stale files left behind from a previous deploy.
Step 3: Configure Nginx
Configure Nginx to serve your website.
-
Create a new Nginx configuration file:
sudo nano /etc/nginx/sites-available/mywebsite -
Add the following configuration, adjusting your domain and paths as necessary:
server { listen 80; server_name yourdomain.com www.yourdomain.com; # serve the build output straight from the repository root /home/youruser/dev/mywebsite/dist; index index.html; location / { try_files $uri $uri/ =404; } error_page 404 /404.html; location = /404.html { internal; } }Note: Nginx runs as the
www-datauser, so it needs permission to traverse into your home directory. If you get a 403 error, allow it withchmod o+x /home/youruser. -
Enable the site and disable the default site:
sudo ln -s /etc/nginx/sites-available/mywebsite /etc/nginx/sites-enabled/ sudo unlink /etc/nginx/sites-enabled/default -
Test and reload Nginx configuration:
sudo nginx -t sudo systemctl reload nginx
Step 4: Install and Configure Cloudflared
Install the Cloudflare Tunnel daemon (cloudflared) and set up a tunnel.
-
Download and install
cloudflared:wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64.deb sudo dpkg -i cloudflared-linux-arm64.deb -
Authenticate
cloudflared:cloudflared tunnel loginFollow the prompts in your browser to log in to your Cloudflare account and select your domain.
-
Create a tunnel:
cloudflared tunnel create mytunnel -
Configure the tunnel:
sudo mkdir /etc/cloudflared sudo nano /etc/cloudflared/config.ymlAdd the following configuration, replacing
mytunnelwith your tunnel name,<UUID>with your tunnel’s UUID, andyourdomain.comwith your domain:tunnel: mytunnel credentials-file: /home/yourusername/.cloudflared/<UUID>.json ingress: - hostname: yourdomain.com service: http://localhost:80 - hostname: www.yourdomain.com service: http://localhost:80 - service: http_status:404 -
Run the tunnel:
cloudflared tunnel --config /etc/cloudflared/config.yml run
Step 5: Configure DNS Records in Cloudflare
-
Log in to your Cloudflare dashboard and go to the DNS settings for your domain.
-
Add CNAME records for your root domain and
wwwsubdomain pointing to your tunnel’s.cfargotunnel.comaddress.- Type:
CNAME - Name:
@(for the root domain) - Target:
<your-tunnel-id>.cfargotunnel.com - Proxy status:
Proxied - TTL:
Auto
Repeat for the
wwwsubdomain:- Type:
CNAME - Name:
www - Target:
<your-tunnel-id>.cfargotunnel.com - Proxy status:
Proxied - TTL:
Auto
- Type:
Step 6: Set up cloudflared as a systemd Service
To ensure cloudflared runs automatically on startup:
-
Create a systemd service file:
sudo nano /etc/systemd/system/cloudflared.service -
Add the following content, adjusting paths and usernames as needed:
[Unit] Description=Cloudflared Tunnel After=network.target [Service] TimeoutStartSec=0 Type=notify ExecStart=/usr/local/bin/cloudflared tunnel --config /etc/cloudflared/config.yml run mytunnel User=yourusername Group=yourusername Restart=on-failure RestartSec=10 [Install] WantedBy=multi-user.target -
Enable and start the service:
sudo systemctl enable cloudflared sudo systemctl start cloudflared
Step 7: Secure Your Server
Enhance your server’s security by setting up a firewall and a brute-force protection tool.
-
Install and configure
ufw(Uncomplicated Firewall):sudo apt install ufw sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow ssh sudo ufw allow http sudo ufw allow https sudo ufw enable -
Install and configure
fail2ban:sudo apt install fail2ban sudo systemctl start fail2ban sudo systemctl enable fail2ban sudo nano /etc/fail2ban/jail.localAdd the following configuration to
jail.local:[DEFAULT] bantime = 3600 banaction = iptables-multiport findtime = 600 maxretry = 3 [sshd] enabled = true [nginx-http-auth] enabled = true port = http,https filter = nginx-http-auth logpath = /var/log/nginx/error.log [nginx-badbots] enabled = true port = http,https filter = nginx-badbots logpath = /var/log/nginx/access.log [nginx-noscript] enabled = true port = http,https filter = nginx-noscript logpath = /var/log/nginx/access.logRestart
fail2ban:sudo systemctl restart fail2ban
Step 8: Updating Your Website
This is where serving the build output directly from the repository pays off. Deploying a new version of your website is nothing more than:
cd ~/dev/mywebsite
git pull
npm run build
The new files are live the moment the build finishes. No copying, no cleanup of old files, and no need to reload Nginx, it simply serves whatever is in dist.
Conclusion
You’ve now successfully set up a web server on your Raspberry Pi using Cloudflare Tunnels, Nginx, and enhanced its security with ufw and fail2ban. This setup provides a secure and efficient way to host your website from home. Remember to keep your system updated and monitor logs regularly to maintain security and performance.
Further Enhancements
- HTTPS: Cloudflare Tunnel automatically provisions SSL certificates, so your site should be accessible via HTTPS.
- Caching: Configure Cloudflare’s caching options to improve performance.
- Dynamic DNS: If your home IP address changes, consider setting up dynamic DNS to update your Cloudflare DNS records automatically.
- Monitoring: Set up monitoring tools to keep an eye on your server’s performance and uptime.
- Auto-deploy: A small webhook service listening for GitHub push events can run the pull-and-build for you on every push to main.
- More sites: One Pi can host several websites, add an extra Nginx server block per domain and route them all through the same Cloudflare tunnel by adding their hostnames to the ingress list.