Final architecture:
example.com -> GitHub Pages
www.example.com -> GitHub Pages
docs.example.com -> GitHub Pages
*.example.com -> Cloudflare Tunnel
Cloudflare Tunnel -> Nginx Proxy Manager
NPM -> Internal Services
ssh.example.com -> SSH over Cloudflare Access
Examples:
api.example.com -> FastAPI
db.example.com -> pgAdmin
git.example.com -> Gitea
grafana.example.com -> Grafana
ssh.example.com -> SSH server
You need:
- Domain in Cloudflare
- GitHub account
- NixOS server
- Docker installed
- Ports NOT forwarded on router
Recommended:
NO public ports
NO DDNS
NO reverse port forwarding
Cloudflare Tunnel handles everything outbound.
Example:
username.github.io
OR any repo.
Enable Pages:
GitHub
→ Repository
→ Settings
→ Pages
Create:
CNAME
Contents:
example.com
Open:
Cloudflare Dashboard
→ DNS
A @ 185.199.108.153
A @ 185.199.109.153
A @ 185.199.110.153
A @ 185.199.111.153
CNAME www username.github.io
CNAME docs username.github.io
These subdomains go directly to GitHub Pages.
CNAME * YOUR_TUNNEL_ID.cfargotunnel.com
Important:
Specific DNS records override wildcard.
Result:
| Domain | Destination |
|---|---|
| docs.example.com | GitHub |
| api.example.com | Tunnel |
| db.example.com | Tunnel |
| anything.example.com | Tunnel |
Edit:
/etc/nixos/configuration.nix
Add:
environment.systemPackages = with pkgs; [
cloudflared
];Rebuild:
sudo nixos-rebuild switchRun:
cloudflared tunnel loginBrowser opens.
Select domain.
Creates:
~/.cloudflared/cert.pem
cloudflared tunnel create homelabExample output:
Tunnel credentials written to:
/root/.cloudflared/UUID.json
Save:
- Tunnel UUID
- JSON path
Create directory:
sudo mkdir -p /etc/cloudflaredCreate:
sudo nano /etc/cloudflared/config.ymlReplace UUID.
tunnel: YOUR_TUNNEL_UUID
credentials-file: /root/.cloudflared/YOUR_TUNNEL_UUID.json
ingress:
# SSH
- hostname: ssh.example.com
service: ssh://localhost:22
# Everything else
- hostname: "*.example.com"
service: http://localhost:80
- service: http_status:404Explanation:
ssh.example.com
-> SSH daemon
everything else
-> NPM
Edit:
/etc/nixos/configuration.nix
Add:
systemd.services.cloudflared = {
description = "Cloudflare Tunnel";
after = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = "${pkgs.cloudflared}/bin/cloudflared --config /etc/cloudflared/config.yml tunnel run";
Restart = "always";
RestartSec = "5s";
};
};Rebuild:
sudo nixos-rebuild switchStart:
sudo systemctl start cloudflaredEnable:
sudo systemctl enable cloudflaredCheck:
systemctl status cloudflaredAdd:
virtualisation.docker.enable = true;
users.users.YOUR_USER.extraGroups = [ "docker" ];Rebuild:
sudo nixos-rebuild switchLogout/login.
Create directory:
mkdir ~/npm
cd ~/npmCreate:
docker-compose.yml
services:
npm:
image: jc21/nginx-proxy-manager:latest
container_name: npm
restart: unless-stopped
ports:
- "80:80"
- "81:81"
- "443:443"
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencryptStart:
docker compose up -dOpen locally:
http://SERVER_IP:81
Default:
admin@example.com
changeme
Change password immediately.
Project:
Example:
api.example.com
Forward to:
192.168.1.10:8000
grafana.example.com
→
192.168.1.20:3000
Open:
Cloudflare Zero Trust Dashboard
Go:
Access
→ Applications
→ Add Application
Choose:
Self-hosted
Application domain:
ssh.example.com
Session duration:
24h
Policy:
Allow
→ Your email
Example:
you@gmail.com
Save.
Linux:
sudo apt install cloudflaredNixOS:
environment.systemPackages = with pkgs; [
cloudflared
];Edit:
~/.ssh/config
Add:
Host ssh.example.com
ProxyCommand cloudflared access ssh --hostname %h
User YOUR_USERModern OpenSSH usually does NOT require:
HostKeyAlgorithms +ssh-rsa
PubkeyAcceptedKeyTypes +ssh-rsa
Only add them if older servers require RSA compatibility.
Now simply run:
ssh ssh.example.comFlow:
SSH Client
↓
cloudflared access ssh
↓
Cloudflare Access Authentication
↓
Tunnel
↓
SSH Server
No:
- Public SSH port
- VPN
- Port forwarding
First connection may open browser for Cloudflare authentication.
After login:
Access token cached locally
Future SSH connections become seamless.
Check logs:
journalctl -u cloudflared -fsudo systemctl restart cloudflaredcloudflared tunnel --config /etc/cloudflared/config.yml rundocker psProtect these with Cloudflare Access:
- NPM dashboard
- Grafana
- pgAdmin
- Portainer
- Gitea
Example:
Allow only:
*@yourdomain.com
Avoid:
Router:
80
443
22
Tunnel removes need entirely.
Your SSH server becomes invisible publicly.
Only Cloudflare edge can reach it.
Huge reduction in attack surface.
example.com
www.example.com
docs.example.com
Automatically handled:
api.example.com
db.example.com
grafana.example.com
git.example.com
ssh.example.com
through Cloudflare Access.