How to Self-Host Gitea or Forgejo on Fedora

Self-host a personal Git server on Fedora by installing Gitea or Forgejo as a native systemd service backed by SQLite or PostgreSQL.

You want a Git server that respects Fedora

You downloaded the Gitea or Forgejo binary and tried to run it. The browser shows a connection refused error. Or the service starts, crashes immediately, and leaves you staring at a blank terminal. You are on Fedora, SELinux is enforcing, and the generic installation guide you found online assumes you are running as root or using Docker. You need a setup that integrates with systemd, respects the security model, and survives a reboot.

What is actually happening

Gitea and Forgejo are Git platforms written in Go. They compile to a single binary. This makes them portable but also easy to misconfigure. The binary needs a database, a web server, and file access. Fedora expects services to run as unprivileged users, managed by systemd, and confined by SELinux.

Running the binary directly as root works until SELinux blocks a network bind or a file write. Running it in a container hides the complexity but adds overhead and breaks integration with the host firewall and user management. The goal is a native service that behaves like any other Fedora daemon. The binary runs as a dedicated user, systemd manages the lifecycle, and SELinux labels protect the data directories.

Install dependencies and database

Install the tools required for Git operations and database storage. SQLite is sufficient for small teams. PostgreSQL scales better for high concurrency and multiple users.

sudo dnf install -y git sqlite
# sqlite is embedded and requires no separate service.
# Install postgresql-server if you prefer a dedicated database engine.

If you choose PostgreSQL, initialize the database and create the user. Fedora's PostgreSQL package requires explicit setup before the first start.

sudo dnf install -y postgresql-server postgresql
sudo postgresql-setup --initdb
# --initdb creates the initial database cluster in /var/lib/pgsql/data.
sudo systemctl enable --now postgresql
sudo -u postgres createuser --pwprompt gitea
# --pwprompt asks for a password interactively.
sudo -u postgres createdb -O gitea gitea
# -O sets the gitea user as the owner of the database.

Run dnf upgrade --refresh weekly to keep dependencies current. This command forces a metadata refresh before upgrading, ensuring you get the latest package versions.

Download and install the binary

Download the binary and place it in the standard local binary directory. Check the official release pages for the latest version number. Forgejo uses the same directory layout and systemd configuration as Gitea.

VERSION=1.22.3
curl -LO https://dl.gitea.com/gitea/${VERSION}/gitea-${VERSION}-linux-amd64
# -L follows redirects. -O saves with the remote filename.
chmod +x gitea-${VERSION}-linux-amd64
# The binary must be executable to run.
sudo mv gitea-${VERSION}-linux-amd64 /usr/local/bin/gitea
# /usr/local/bin is the standard path for locally installed binaries.

Updating the binary requires manual intervention. dnf does not manage files in /usr/local/bin. Download the new version, replace the file, and restart the service. Backup the database and /var/lib/gitea/data before replacing the binary. A schema change in the new version can corrupt data if the migration fails.

Create the service user and directories

Create a dedicated system user to isolate the service from the rest of the system. The service should never run as root. The user needs a home directory for data and a group for configuration access.

sudo useradd -r -s /sbin/nologin -d /var/lib/gitea gitea
# -r creates a system account with a UID below 1000.
# -s /sbin/nologin prevents interactive shell access.
# -d sets the home directory for the service user.
sudo mkdir -p /var/lib/gitea/{custom,data,log}
# custom holds themes and templates. data holds repositories. log holds logs.
sudo chown -R gitea:gitea /var/lib/gitea
sudo chmod -R 750 /var/lib/gitea
# 750 restricts access to the user and group only.
sudo mkdir -p /etc/gitea
sudo chown root:gitea /etc/gitea
sudo chmod 770 /etc/gitea
# The config directory is owned by root but writable by the gitea group.

Config files live in /etc/. Package files live in /usr/lib/. Edit /etc/. Never edit /usr/lib/. Changes in /usr/lib/ get overwritten on package updates. Changes in /etc/ persist.

Configure systemd

Define the systemd unit file that manages the service lifecycle. The unit file tells systemd how to start the process, which dependencies to wait for, and how to restart on failure.

sudo tee /etc/systemd/system/gitea.service > /dev/null <<'EOF'
[Unit]
Description=Gitea Git Service
After=network.target postgresql.service
# Wait for network and database before starting.
# Remove postgresql.service if using SQLite.

[Service]
User=gitea
Group=gitea
WorkingDirectory=/var/lib/gitea
ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini
Restart=always
Environment=USER=gitea HOME=/var/lib/gitea
# Gitea uses HOME for path resolution. Set it explicitly.
# Restart=always ensures the service recovers from crashes.

[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
# daemon-reload makes systemd aware of the new unit file.
sudo systemctl enable --now gitea
# enable creates the symlink for boot. --now starts the service immediately.

Check the service status before proceeding. systemctl status shows recent log lines and the current state in one view.

sudo systemctl status gitea
# Look for "active (running)" and recent log lines.

Open the firewall

Open the firewall to allow external connections to the Gitea web interface. Fedora's firewall daemon blocks incoming traffic by default.

sudo firewall-cmd --permanent --add-port=3000/tcp
# --permanent adds the rule to the persistent configuration.
sudo firewall-cmd --reload
# Reload applies the permanent rule to the runtime configuration.
# Always reload after changing permanent rules.

Visit http://<your-server-ip>:3000 in a browser. The web installer will guide you through database configuration and admin account creation. The installer writes the configuration to /etc/gitea/app.ini. If the installer fails, check the logs. The service might not have permission to write to the config directory.

Handle SELinux

Fedora ships with SELinux enforcing by default. SELinux denies access based on context, not just permissions. If Gitea cannot bind to its port or write to its data directory, check for AVC denials.

type=AVC msg=audit(1700000000.000:100): avc:  denied  { name_bind } for  pid=1234 comm="gitea" src=3000 scontext=system_u:system_r:unconfined_t:s0 tcontext=system_u:object_r:unreserved_port_t:s0 tclass=tcp_socket

The error shows a denial on name_bind for port 3000. SELinux labels ports by type. Port 3000 is not labeled as an HTTP port by default. Adjust the port label to allow the service to bind.

sudo semanage port -a -t http_port_t -p tcp 3000
# -a adds a new label. -t sets the type to http_port_t.
# This allows processes with the http context to bind to port 3000.
sudo systemctl restart gitea
# Restart the service to apply the change.

If you copy files into /var/lib/gitea, the SELinux context might be wrong. Run restorecon -Rv /var/lib/gitea to fix labels. The restorecon command resets contexts to the default values defined by the policy.

Run journalctl -t setroubleshoot first. Read the denial before disabling SELinux. Disabling SELinux removes a critical security layer. Fixing the policy is safer and more precise.

Verify the installation

Inspect the service logs to diagnose startup failures or runtime errors. The journal stores logs for all systemd units.

sudo journalctl -xeu gitea
# -x adds explanatory text for error codes.
# -e jumps to the end of the log.
# -u filters for the gitea unit.

Check the web interface. Create a test repository. Push a commit. Pull the commit. Verify that the database connection holds and file permissions remain correct. If the service crashes under load, check the LimitNOFILE setting in the systemd unit. Gitea may need more file descriptors for many repositories.

Add LimitNOFILE=65535 to the [Service] section of the unit file if you see too many open files errors. Reload systemd and restart the service after editing the unit file.

Common pitfalls

The service fails to start with Failed to start Gitea Git Service. Check the logs. A common error is bind: address already in use. Another process is using port 3000. Run sudo ss -tlnp | grep 3000 to find the conflicting process.

The web installer shows a database error. Verify the database credentials in the installer. If using PostgreSQL, ensure the pg_hba.conf file allows connections from the Gitea user. Fedora's default configuration might restrict local connections.

The binary update breaks the service. The new version might require a database migration. Run the migration manually if the service fails to start. The binary includes a migration command. Run sudo -u gitea /usr/local/bin/gitea migrate to apply schema changes.

Config drift occurs when you edit the binary or copy files without preserving permissions. Always use chown and chmod after manual changes. Use restorecon to fix SELinux contexts.

Backup the database and data directory before any major change. A botched upgrade can leave you unable to access repositories. Run pg_dump for PostgreSQL or copy the SQLite file for SQLite. Compress the backup and store it off-site.

Decision matrix

Use Gitea when you want a lightweight Git server with a large community and frequent upstream updates.

Use Forgejo when you prefer a community-governed fork that focuses on stability and avoids corporate influence.

Use GitHub when you need maximum ecosystem integration and are willing to pay for storage or accept data collection.

Use GitLab when you require built-in CI/CD pipelines and issue tracking at the cost of higher resource usage.

Use bare Git over SSH when you only need version control and want zero overhead.

Snapshot the system before the upgrade. Future-you will thank you.

Where to go next