Hosting Gitea & Forgejo with Docker, Nginx, and Cloudflare Proxy
This guide helps beginners deploy Gitea or Forgejo Git servers using Docker containers, secure them with Nginx reverse proxy, and protect with Cloudflare.
Replace all example domains like git.yourdomain.com
with your real domain or subdomain.
Step 1: Project Directory and File Structure
Connect to your VPS via SSH and create a main project directory:
ssh your_user@your_vps_ip
mkdir -p ~/git-server/{gitea-data,forgejo-data,certs}
cd ~/git-server
Structure explanation:
gitea-data/
orforgejo-data/
: persistent volumes where application and database data are stored, surviving container restarts.certs/
: stores SSL certificates from Certbot.The main directory holds your Docker Compose and Nginx config files.
This clear separation helps with backups and easy upgrades.
Step 2: Docker Compose Files (Separate for Each Service)
Gitea Docker Compose (docker-compose-gitea.yml
)
docker-compose-gitea.yml
)version: "3"
services:
gitea:
image: gitea/gitea:latest
container_name: gitea
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__server__DOMAIN=git.yourdomain.com
- GITEA__server__ROOT_URL=https://git.yourdomain.com/
- GITEA__server__REVERSE_PROXY_TRUSTED_PROXIES=127.0.0.1 # Add Cloudflare IP ranges
volumes:
- ./gitea-data:/data
networks:
- git-net
restart: always
nginx:
image: nginx:latest
container_name: nginx-gitea
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx-gitea.conf:/etc/nginx/conf.d/default.conf:ro
- ./certs:/etc/nginx/certs:ro
depends_on:
- gitea
networks:
- git-net
restart: always
networks:
git-net:
driver: bridge
Forgejo Docker Compose (docker-compose-forgejo.yml
)
docker-compose-forgejo.yml
)version: "3"
services:
forgejo:
image: codeberg.org/forgejo/forgejo:latest
container_name: forgejo
environment:
- USER_UID=1000
- USER_GID=1000
- FORGEJO_APP__server__ROOT_URL=https://git.yourdomain.com/
- FORGEJO_APP__server__TRUSTED_PROXIES=127.0.0.1 # Add Cloudflare IP ranges
volumes:
- ./forgejo-data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
networks:
- git-net
restart: always
nginx:
image: nginx:latest
container_name: nginx-forgejo
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx-forgejo.conf:/etc/nginx/conf.d/default.conf:ro
- ./certs:/etc/nginx/certs:ro
depends_on:
- forgejo
networks:
- git-net
restart: always
networks:
git-net:
driver: bridge
Note: Add the latest Cloudflare IP ranges explicitly in
REVERSE_PROXY_TRUSTED_PROXIES
orTRUSTED_PROXIES
environment variables to ensure accurate client IP logging and security.
Step 3: Advanced Nginx Configuration
Your Nginx config files must support important features:
Proxy headers to pass the correct client IP and protocol.
WebSocket support for real-time features.
Static file caching to improve performance.
Client body size limit to prevent abuse.
Error and access logging for troubleshooting.
Example for Gitea (nginx-gitea.conf
):
nginx-gitea.conf
):server {
listen 80;
server_name git.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name git.yourdomain.com;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers HIGH:!aNULL:!MD5;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
add_header Referrer-Policy "no-referrer";
add_header Content-Security-Policy "default-src 'self';";
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
client_max_body_size 100M;
location / {
proxy_pass http://gitea:3000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 36000s;
proxy_send_timeout 36000s;
}
location /assets/ {
root /data/gitea/public/;
expires max;
}
error_log /var/log/nginx/gitea_error.log warn;
access_log /var/log/nginx/gitea_access.log combined;
}
The Forgejo config is analogous but proxies to http://forgejo:3000/
.
Step 4: Cloudflare SSL & Firewall Integration
Use these Cloudflare settings to avoid common SSL errors:
SSL/TLS mode: set to Full (strict) to require your VPS to have a valid SSL cert.
Enable Always Use HTTPS to redirect all HTTP to HTTPS.
Upload your domain's SSL certificates with Certbot (next step) to avoid 525 SSL handshake errors.
Configure your VPS firewall (UFW) to allow inbound HTTP/HTTPS only from Cloudflare's IPs:
curl https://www.cloudflare.com/ips-v4 -o cloudflare-ips-v4.txt
curl https://www.cloudflare.com/ips-v6 -o cloudflare-ips-v6.txt
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
for ip in $(cat cloudflare-ips-v4.txt); do
sudo ufw allow from $ip to any port 80,443 proto tcp
done
for ip in $(cat cloudflare-ips-v6.txt); do
sudo ufw allow from $ip to any port 80,443 proto tcp
done
sudo ufw enable
sudo ufw reload
Automation tip: Write a script to periodically update UFW rules when Cloudflare IPs change.
Step 5: SSL Certificates with Certbot & Renewal Automation
Install Certbot and get certificates for your domain:
sudo apt update
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d git.yourdomain.com
Certbot installs certificates in your local certs/
folder (mounted to Nginx).
Set up automatic renewal by editing the root crontab:
sudo crontab -e
Add the following line to renew certs daily and reload Nginx if renewed:
0 3 * * * certbot renew --post-hook "docker-compose -f docker-compose-gitea.yml restart nginx"
Replace docker-compose-gitea.yml
with your compose file if different.
Step 6: Important Application-Level Configuration for Gitea & Forgejo
Trusted Proxies
Add Cloudflare's IP ranges to trusted proxies so Gitea/Forgejo log true client IPs and prevent header spoofing.
Gitea uses
GITEA__server__REVERSE_PROXY_TRUSTED_PROXIES
Forgejo uses
FORGEJO_APP__server__TRUSTED_PROXIES
You can list IPs or CIDR ranges separated by commas.
Custom Security Settings
Edit app.ini
inside your persistent data volume or via UI after first run to:
Disable open user registrations
Configure SMTP settings for email notifications
Persistent Databases
Although SQLite (default) works for small setups, consider PostgreSQL or MySQL for durability:
For Forgejo, similar database integration is recommended - https://forgejo.org/docs/latest/admin/config-cheat-sheet/
Store database files in volumes for persistence
Volume Backup
Regularly back up your gitea-data/
or forgejo-data/
folders:
tar czf gitea-backup-$(date +%F).tar.gz gitea-data/
Or use other backup strategies matching your needs.
Step 7: Monitoring and Log Management
Logs
Check logs to troubleshoot errors:
Nginx logs inside the container (mapped by default to
/var/log/nginx/
)Docker container logs:
docker logs -f gitea
# or for forgejo
docker logs -f forgejo
# For nginx
docker logs -f nginx-gitea
Log Rotation
Logs can grow large. Implement log rotation on your VPS:
Use system tools like
logrotate
to compress and delete old logs.Rotate Docker logs by configuring Docker daemon or mounting external log directories.
Automate Cloudflare IP Updates
Cloudflare IPs can change. Automate firewall updates by scripting the download of IP lists and updating UFW rules on a schedule using cron.
Step 8: Troubleshooting Common Issues
Push failures or 500 errors often indicate proxy or SSL misconfiguration.
Review Nginx error logs and container logs to pinpoint errors.
Confirm SSL cert validity and Nginx properly passes HTTP/HTTPS headers.
Check that trusted proxies in app settings include Cloudflare IPs, or client IPs will be unknown.
If WebSocket features fail, verify
proxy_set_header Upgrade
and connection headers are set.
When in doubt, restart containers and services after config changes:
docker-compose -f docker-compose-gitea.yml restart
# or
docker-compose -f docker-compose-forgejo.yml restart
Use logs to iteratively identify and fix errors.
Step 9: Starting Your Deployment
From your project directory:
To start Gitea:
docker-compose -f docker-compose-gitea.yml up -d
To start Forgejo:
docker-compose -f docker-compose-forgejo.yml up -d
Visit https://git.yourdomain.com/ to complete initial web-based setup.
Set up admin users, email notifications, and disable open registrations for security.
Summary
Project Dir Structure
Organize Docker, Nginx configs and persistent volume folders
Docker Compose
Separate files per service, map volumes, set trusted proxies
Nginx Reverse Proxy
Pass proxy headers, WebSocket support, static file caching, error logging
Cloudflare
Full (strict) SSL, Always HTTPS, IP whitelist Firewall + DNS proxying
Firewall
UFW allows only Cloudflare IPs on HTTP/HTTPS ports
SSL Certs
Install and auto-renew with Certbot
App Config (Gitea/Forgejo)
Trusted proxies, disable open registration, persistent DB
Logs & Maintenance
Monitor container and Nginx logs, rotate logs, automate IP updates
Troubleshooting
Use verbose logging, check proxy & SSL settings for errors
Last updated