01Introduction
Self-hosting is most useful when you can access services from anywhere. But exposing services to the internet is risky. This guide covers secure methods for remote access, from simple to advanced.
02Know the Risks
Before exposing anything, understand what you're opening up to. The internet is constantly scanned for vulnerable services. Any exposed port will be probed within minutes.
Exposed services will be attacked. Automated bots scan every IP address continuously. Your service must be secured before exposure.
03Always Use a Reverse Proxy
Never expose container ports directly. Always put a reverse proxy in front. This provides SSL termination, access logging, and a single point of security.
1# WRONG: Exposing container directly2services: 3 app: 4 ports: 5 - "8080:80" # Accessible from internet if firewall allows67# RIGHT: Behind reverse proxy8services: 9 traefik: 10 ports: 11 - "443:443" # Only proxy is exposed1213 app: 14 # No ports exposed15 labels: 16 - "traefik.enable=true"17 - "traefik.http.routers.app.rule=Host(`app.example.com`)"18 - "traefik.http.routers.app.tls=true"04Add Authentication
If the service doesn't have built-in authentication, add it at the proxy level. Never expose unauthenticated services.
1# Traefik with basic auth2services: 3 app: 4 labels: 5 - "traefik.http.routers.app.rule=Host(`app.example.com`)"6 - "traefik.http.middlewares.app-auth.basicauth.users=admin:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/"7 - "traefik.http.routers.app.middlewares=app-auth"89# Generate password hash:10# htpasswd -nb admin yourpassword11# Escape $ as $$ in compose files1213# For better security, use Authelia or Authentik14# for SSO with 2FA supportConsider Authelia or Authentik for proper SSO with two-factor authentication instead of basic auth.
05Cloudflare Tunnel (Zero Trust)
Cloudflare Tunnel exposes services without opening firewall ports. Traffic goes through Cloudflare, providing DDoS protection and hiding your IP.
1services: 2 tunnel: 3 image: cloudflare/cloudflared:latest4 command: tunnel run5 environment: 6 - TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN}7 networks: 8 - internal910 app: 11 image: myapp:1.012 networks: 13 - internal1415networks: 16 internal: 1718# Setup:19# 1. Create tunnel in Cloudflare Zero Trust dashboard20# 2. Configure public hostname -> http://app:8021# 3. No firewall ports needed!2223# Benefits:24# - No exposed ports25# - Built-in DDoS protection26# - Hides your real IP27# - Cloudflare Access for authCloudflare Tunnel is free and doesn't require opening any inbound ports. It's excellent for home setups with dynamic IPs.
06Tailscale/WireGuard VPN
Instead of exposing to the public internet, use a VPN. Only devices on your VPN network can access services.
1# Tailscale sidecar approach2services: 3 tailscale: 4 image: tailscale/tailscale:latest5 hostname: myserver6 environment: 7 - TS_AUTHKEY=${TAILSCALE_AUTHKEY}8 - TS_STATE_DIR=/var/lib/tailscale9 - TS_USERSPACE=false10 volumes: 11 - tailscale_data:/var/lib/tailscale12 - /dev/net/tun:/dev/net/tun13 cap_add: 14 - NET_ADMIN15 - NET_RAW16 network_mode: service:app # Share network with app1718 app: 19 image: myapp:1.020 # Accessible via Tailscale IP/hostname2122volumes: 23 tailscale_data: 2425# Access from any Tailscale-connected device:26# http://myserver:80 or http://100.x.x.x:8007Firewall Configuration
If you must expose ports publicly, restrict access with firewall rules. Only open what's necessary.
1# UFW (Ubuntu)2# Only allow SSH and HTTPS3ufw default deny incoming4ufw default allow outgoing5ufw allow 22/tcp6ufw allow 443/tcp7ufw enable89# Allow specific IP only10ufw allow from 203.0.113.0/24 to any port 221112# iptables13iptables -A INPUT -p tcp --dport 443 -j ACCEPT14iptables -A INPUT -p tcp --dport 22 -j ACCEPT15iptables -A INPUT -j DROP1617# Docker bypasses UFW by default!18# Fix by editing /etc/docker/daemon.json:19{20 "iptables": false21}22# Then manage Docker traffic manuallyDocker modifies iptables directly and can bypass UFW rules. Research 'Docker UFW' for your setup.
08Fail2ban for Brute Force Protection
Fail2ban monitors logs and bans IPs that show malicious behavior like repeated failed logins.
1# Install2apt install fail2ban34# Docker-aware configuration5# /etc/fail2ban/jail.local6[traefik-auth]7enabled = true8filter = traefik-auth9logpath = /var/log/traefik/access.log10maxretry = 511bantime = 36001213# /etc/fail2ban/filter.d/traefik-auth.conf14[Definition]15failregex = ^<HOST> - .* 401 .*$1617# Restart18systemctl restart fail2ban1920# Check status21fail2ban-client status traefik-auth09Geographic Restrictions
If you only access from certain countries, block the rest. Reduces attack surface significantly.
1# With Traefik and GeoIP plugin2services: 3 traefik: 4 labels: 5 # Whitelist countries6 - "traefik.http.middlewares.geoblock.plugin.geoblock.countries=US,CA,GB"78# With Cloudflare (if using tunnel/proxy)9# Configure in Cloudflare dashboard:10# Security -> WAF -> Create rule -> Country not in [list] -> Block1112# With nginx (GeoIP module)13# geoip_country /usr/share/GeoIP/GeoIP.dat;14# map $geoip_country_code $allowed_country {15# default no;16# US yes;17# CA yes;18# }10Monitoring and Alerts
You must monitor exposed services. Set up alerts for unusual activity.
1# Uptime Kuma for monitoring2services: 3 uptime-kuma: 4 image: louislam/uptime-kuma:latest5 volumes: 6 - uptime_data:/app/data7 labels: 8 - "traefik.http.routers.status.rule=Host(`status.example.com`)"910# Set up monitors for:11# - HTTP/HTTPS endpoints12# - SSL certificate expiry13# - Response time thresholds14# - Keyword presence1516# Configure notifications:17# - Email18# - Slack/Discord19# - Pushover/Gotify20# - WebhookMonitor from outside your network to catch issues that internal checks would miss.
11Exposure Checklist
Before exposing any service:
- [ ] Service behind reverse proxy
- [ ] HTTPS enabled with valid certificate
- [ ] Authentication required (at app or proxy level)
- [ ] Firewall configured (only necessary ports)
- [ ] Fail2ban or similar protection
- [ ] Monitoring and alerts set up
- [ ] Regular security updates planned
- [ ] Backup strategy in place
- [ ] Know how to quickly disable access if compromised