Table of Contents
In this project, I will be deploying Forgejo on a Virtual Private Server (VPS). Forgejo is a self-hosted Git server that allows you to manage your own Git repositories. It is a great alternative to GitHub and GitLab.
You can poke around at git.jackwaterloo.com to see what it looks like before you start.
What you need to follow along
- A Virtual Private Server (VPS)
- A domain name
- A way to connect to the VPS
What I used to set up the server
- VPS: I used IONOS for my VPS.
- VPS Operating System: AlmaLinux 9.
- Domain Name Registrar: Porkbun.
- Cloudflare for DNS service.
Make sure you have all the above items before you start.
Pretty much all of my directions will be for AlmaLinux 9 or RHEL based distributions. If you are using a different distribution, you will need to adjust the commands accordingly.
DNS Setup
I like to set up DNS first so we can access the server by domain name instead of by IP. You can do this later, but doing it early avoids using the IP to connect. After purchasing a domain, add the DNS records at your registrar or a DNS provider (I use Cloudflare). You will also need your VPS’s IP from your provider.
Add a DNS A record for your domain pointing to the VPS’s IP address.
Securing Server
I received most of this information from this blog post that I found from Dreams of Code Youtube channel.
You should have received a default root password when you first signed up with your VPS provider.
Change the root password first to reduce brute-force risk, since default passwords are often well known. Run:
passwd rootEnter the new password when prompted and confirm it.
To add another layer of security, create a dedicated user with sudo for daily use. That way you avoid using root for normal tasks, and sudo will require a password. I use the username jack.
sudo useradd -m -s /bin/bash jack # create new usersudo usermod -aG wheel jack # add to wheel group for sudo# wheel is a group that has sudo privileges in RHEL distributions.Set the password for the new user:
passwd jackWe will use this user for SSH, so create an SSH key on your local machine:
ssh-keygen -t ed25519 -C "jack@vps" # -C is the comment for the key. Name it something descriptive.This creates an SSH key pair in ~/.ssh.
Copy the public key to the server:
ssh-copy-id -i ~/.ssh/id_ed25519.pub jack@git.example.comLog back into the server:
ssh jack@git.example.comWith the public key on the server, restrict SSH to key-based auth, disable root login, and disable Kerberos to speed up logins. On RHEL, use a drop-in under /etc/ssh/sshd_config.d/. For example:
nano /etc/ssh/sshd_config.d/01-custom-security.conf
# 1. Force SSH Key usage and disable passwords# This overrides any 'yes' settings in later filesPasswordAuthentication noAuthenticationMethods publickey
# 2. Disable root login for securityPermitRootLogin no
# 3. Performance: Disable Kerberos lookups to speed up loginGSSAPIAuthentication noThe 01 prefix makes this file take priority over higher-numbered files in the same directory.
Confirm your public key is on the server:
cat ~/.ssh/authorized_keysTest the SSH config syntax (the output will show if settings are applied):
sudo sshd -tRestart sshd to apply the changes:
sudo systemctl restart sshdSSH is locked down; next, add a firewall. AlmaLinux 9 does not install one by default. IONOS provides a gateway firewall for the server, but I prefer to manage one on the host. Firewalld is the default on RHEL. Install it with:
sudo dnf install -y firewalldOpen SSH, HTTP, and HTTPS so the server can accept that traffic:
# Open SSH, HTTP, HTTPS (persistent)sudo firewall-offline-cmd --add-service={ssh,http,https}Start and enable firewalld:
sudo systemctl start firewalld && sudo systemctl enable firewalldNow that the server is secure, we can start the setup for Forgejo.
Forgejo Setup
We will deploy Forgejo with Docker. Install Docker using the official docs; for AlmaLinux, use the RHEL instructions.
Forgejo will run as a dedicated user inside the container; that same user is used for cloning and pushing. To avoid using root or a sudo-capable user, create a git user (a common convention for Git servers). Create it with:
# Dedicated 'git' system user for Forgejo (or similar git servers):# - --system: system user (non-login, lower UID)# - --create-home: create home dir if missing# - --home-dir /home/git/: home directory path# - --shell /bin/bash: login shell# - --comment "Git Version Control": user description# - --user-group: group 'git' with this user as sole member# - --no-log-init: no skel files in home (minimal env)# - --password *: disables password login for this usersudo useradd \ --system \ --create-home \ --home-dir /home/git/ \ --shell /bin/bash \ --comment "Git Version Control" \ --user-group \ --no-log-init \ --password "*" \ gitsudo usermod -aG docker git # add git to the docker groupCreate the directory (sudo mkdir -p /home/jack/forgejo/) and add a Docker Compose file (I got it from the Forgejo site). Get the correct UID and GID for the git user by running:
id git.
networks: forgejo: external: false
services: server: image: codeberg.org/forgejo/forgejo:14 container_name: forgejo environment: - USER_UID=1000 #UID of the git user - USER_GID=1000 #GID of the git user restart: always networks: - forgejo volumes: - ./forgejo:/data - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro ports: - "3000:3000" - "222:22"So both the git user and your account can access the Forgejo files under /home/jack/forgejo, set permissions like this:
# Add jack to group git (use group name or GID 993)sudo usermod -aG git jack
cd /home/jack/forgejo# Recursive: owner jack, group git (993)sudo chown -R jack:git .# Group can read/write/execute; others nonesudo chmod -R g+rwX,o= .# So new files created inside get group gitsudo find . -type d -exec chmod g+s {} \;Do not start the container yet. Set up Nginx first to proxy traffic to Forgejo.
Nginx Setup
The Forgejo container will not be exposed directly; we will put Nginx in front as a reverse proxy. Install Nginx using your distro’s method; for RHEL, see Nginx’s RHEL packages.
Install Certbot to obtain and auto-renew an SSL certificate. With Cloudflare for DNS, use the certbot-dns-cloudflare plugin. On RHEL, enable EPEL first, then install Certbot and the plugin:
sudo dnf install epel-releasesudo dnf install certbot python3-certbot-nginx python3-certbot-dns-cloudflareForgejo’s docs describe setting upNginx as a reverse proxy. Create a basic Nginx server block over HTTP first (we’ll add HTTPS next). Replace the URL with your Forgejo domain.
server { listen 80; # Listen on IPv4 port 80 listen [::]:80; # Listen on IPv6 port 80
server_name git.example.com; # Change this to the server domain name.
location / { proxy_pass http://127.0.0.1:3000; # Port 3000 is the default Forgejo port
proxy_set_header Connection $http_connection; proxy_set_header Upgrade $http_upgrade; 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;
client_max_body_size 512M; }}By default, SELinux prevents Nginx from making network connections to other services. Run this to allow it:
sudo setsebool -P httpd_can_network_connect 1Start Nginx and Forgejo
Start both Nginx and Forgejo so you can complete the initial admin setup over HTTP. We’ll enable HTTPS after that.
sudo systemctl start nginxsudo systemctl enable nginx
cd /home/jack/forgejo # Change path if your docker-compose.yml is elsewheresudo docker compose up -dOpen a browser and go to your Forgejo URL right away (use HTTP for now, e.g. http://git.example.com). The initial admin setup is publicly accessible, so complete it quickly.
Then enable HTTPS with Certbot. Ensure the site loads at your domain before continuing.
sudo certbot certonly \ --dns-cloudflare \ --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \ --dns-cloudflare-propagation-seconds 60 \ --installer nginx \ -d git.example.comUpdate the Forgejo app.ini so ROOT_URL (and related base URL settings) uses https://. You may need to bring the container down, edit the file, then start it again for changes to apply.
cd /home/jack/forgejosudo docker compose downsudo nano /home/jack/forgejo/forgejo/gitea/conf/app.ini[server]SSH_DOMAIN = git.example.com # Change to your domain nameDOMAIN = git.example.com # Change to your domain nameHTTP_PORT = 3000ROOT_URL = https://git.example.com/ # Change to https:// if you have set up SSL/CertbotRestart the Forgejo container so the updated app.ini is applied:
sudo docker compose -f /home/jack/forgejo/docker-compose.yml up -dHTTPS should now be working!
Git clone over SSH
The VPS already runs SSH on port 22. Proxying SSH to the Forgejo container is possible but adds complexity I chose to avoid. We can instead expose Forgejo’s built-in SSH server. In our docker-compose.yml file, we have port 222 exposed for the SSH server.
Allow that port through the firewall:
sudo firewall-cmd --permanent --add-port=222/tcpReload firewalld:
sudo firewall-cmd --reloadSet the SSH port in app.ini so the clone URL shown in the Forgejo UI is correct. See the config cheat sheet.
cd /home/jack/forgejosudo docker compose downsudo nano /home/jack/forgejo/forgejo/gitea/conf/app.ini[server]SSH_PORT = 222Start the Forgejo container again to apply the change:
sudo docker compose -f /home/jack/forgejo/docker-compose.yml up -dOverview of the setup
- Nginx listens on ports 80 and 443 and proxies to the Forgejo container on port 3000.
- Forgejo’s SSH server is exposed on port 222.
- Certbot provides the SSL certificate and automatic renewal.