Zero trust security means “never trust, always verify” — no VPN required, no open SSH ports on the public internet, every access request authenticated regardless of network. Implementing zero trust on your Hong Kong VPS combines Cloudflare Tunnel (no open inbound ports) with Authelia (SSO with MFA) to give your team secure access to internal tools — Gitea, Grafana, Portainer, n8n, Jupyter — without exposing any port to the public internet.
Architecture
<code">
Team Member Browser
│
▼
Cloudflare (auth check) ──── Authelia (MFA verification) ────┐
│ │
│ Authenticated │
▼ │
Cloudflare Tunnel ◄──── HK VPS (no public inbound ports) ───┘
│
▼
Internal Services (Gitea, Grafana, Portainer, n8n...)
No inbound ports (not even 443) need to be open on your VPS — Cloudflare Tunnel makes outbound connections to Cloudflare’s network, and Cloudflare routes authenticated traffic through those tunnels to your services.
Step 1: Cloudflare Tunnel Setup
<code"># Install cloudflared curl -L --output cloudflared.deb \ https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb dpkg -i cloudflared.deb # Authenticate with Cloudflare (opens browser) cloudflared tunnel login # Create a tunnel cloudflared tunnel create hk-vps-tunnel # Note the tunnel ID — you'll need it in config cloudflared tunnel list
Tunnel Configuration
<code">mkdir -p /etc/cloudflared/
cat > /etc/cloudflared/config.yml << 'EOF'
tunnel: YOUR_TUNNEL_ID
credentials-file: /root/.cloudflared/YOUR_TUNNEL_ID.json
ingress:
# Gitea — internal Git server
- hostname: git.yourdomain.com
service: http://localhost:3000
# Grafana — monitoring dashboards
- hostname: metrics.yourdomain.com
service: http://localhost:3000 # Grafana port
# Portainer — Docker management
- hostname: docker.yourdomain.com
service: http://localhost:9443
originRequest:
noTLSVerify: true
# n8n — workflow automation
- hostname: automation.yourdomain.com
service: http://localhost:5678
# Authelia — authentication portal (must be first to match)
- hostname: auth.yourdomain.com
service: http://localhost:9091
# Catch-all (required)
- service: http_status:404
EOF
# Create DNS records pointing to the tunnel
cloudflared tunnel route dns hk-vps-tunnel git.yourdomain.com
cloudflared tunnel route dns hk-vps-tunnel metrics.yourdomain.com
cloudflared tunnel route dns hk-vps-tunnel docker.yourdomain.com
cloudflared tunnel route dns hk-vps-tunnel auth.yourdomain.com
# Systemd service
cloudflared service install
systemctl enable --now cloudflaredStep 2: Authelia — Self-Hosted SSO with MFA
<code">mkdir -p /opt/authelia/{config,data}
cat > /opt/authelia/config/configuration.yml << 'EOF' server: host: 0.0.0.0 port: 9091 log: level: info jwt_secret: GENERATE_RANDOM_64_CHAR_STRING default_redirection_url: https://git.yourdomain.com authentication_backend: file: path: /config/users_database.yml password: algorithm: argon2id access_control: default_policy: deny rules: # Allow all authenticated team members to Gitea - domain: git.yourdomain.com policy: one_factor # Require 2FA for admin tools - domain: docker.yourdomain.com policy: two_factor - domain: metrics.yourdomain.com policy: two_factor - domain: automation.yourdomain.com policy: two_factor session: name: authelia_session secret: ANOTHER_RANDOM_64_CHAR_STRING expiration: 8h inactivity: 30m domain: yourdomain.com # Shared session across all subdomains regulation: max_retries: 3 find_time: 2m ban_time: 5m storage: local: path: /data/db.sqlite3 notifier: smtp: host: localhost port: 25 sender: authelia@yourdomain.com EOF # Create users database cat > /opt/authelia/config/users_database.yml << 'EOF'
users:
alice:
displayname: "Alice Chen"
password: "$argon2id$v=19$..." # Generate with: authelia crypto hash generate argon2
email: alice@yourcompany.com
groups:
- admins
- developers
bob:
displayname: "Bob Liu"
password: "$argon2id$v=19$..."
email: bob@yourcompany.com
groups:
- developers
EOF
docker run -d \
--name authelia \
--restart unless-stopped \
-p 127.0.0.1:9091:9091 \
-v /opt/authelia/config:/config \
-v /opt/authelia/data:/data \
authelia/authelia:latestStep 3: Cloudflare Access Rules
In Cloudflare Zero Trust → Access → Applications → Add Application:
- Select: Self-hosted
- Application domain:
git.yourdomain.com - Session duration: 8 hours
- Identity providers: Google Workspace (or any OAuth provider)
Create a Policy:
- Action: Allow
- Include: Emails ending in
@yourcompany.com(or specific email list)
Repeat for each internal service. Now when a team member visits https://git.yourdomain.com:
- Cloudflare checks if they have a valid Access session
- If not, redirect to your identity provider (Google/GitHub OAuth)
- After OAuth authentication, check the email domain policy
- If authorised, route to Authelia for optional MFA
- Final Authelia session grants access to the internal service
Step 4: TOTP Setup for Team Members
<code"># Generate TOTP secret for a user via Authelia CLI docker exec authelia authelia storage totp generate \ --config /config/configuration.yml alice # Output: TOTP URL and QR code # Team member scans with Google Authenticator or Authy # Or users self-enroll at: # https://auth.yourdomain.com/settings/two-factor
Step 5: Close All Public Firewall Ports (Except SSH)
<code"># With Cloudflare Tunnel handling all web traffic, close everything ufw default deny incoming ufw default allow outgoing # Keep SSH on your custom port for emergency server access ufw allow 22222/tcp comment 'Emergency SSH access' # REMOVE web ports — Cloudflare Tunnel handles these ufw delete allow 80/tcp ufw delete allow 443/tcp ufw enable ufw status # Verify tunnel is still working: curl https://git.yourdomain.com # Should reach Gitea via Cloudflare Tunnel
With no public ports 80 or 443 open, your VPS IP cannot be targeted by direct-IP attacks, port scanners, or brute-force attempts on your web services — only SSH remains accessible from the public internet.
Benefits Summary
| Capability | Before (open ports) | After (zero trust) |
|---|---|---|
| Web service ports exposed | 80, 443 public | None (tunnel only) |
| Authentication | Service-specific passwords | SSO + MFA for all services |
| Access logging | Per-service logs | Centralised Cloudflare Access logs |
| Brute force protection | Fail2Ban per service | Cloudflare’s global network blocks attackers |
| New team member access | Create credentials in each service | Add email to Cloudflare Access policy |
| Offboard team member | Remove from each service | Remove from Cloudflare Access policy |
Conclusion
Zero trust access with Cloudflare Tunnel and Authelia eliminates the attack surface on your Hong Kong VPS — no public web ports, all team access authenticated via SSO with MFA, every access attempt logged centrally. The combination of Cloudflare’s global DDoS protection and Authelia’s local authentication gives teams secure access to internal tools from anywhere in Asia-Pacific without a traditional VPN, while keeping the VPS hardened against direct attacks.
Secure your team access: Browse Server.HK Hong Kong VPS plans — Cloudflare Tunnel and Authelia run on any VPS plan with minimal resource overhead alongside your application services.