How To Install GitLab on Fedora 44

Install GitLab on Fedora 44

Fedora ships fast, and that is exactly why GitLab and Fedora make an awkward couple. GitLab Inc. publishes official Linux packages for Enterprise Linux clones, Debian and Ubuntu. Its own documentation lists Fedora under unsupported distributions: installation is possible, but not supported. So anyone typing “install GitLab on Fedora 44” is already off the paved road.

That does not make it a bad idea. Plenty of teams run Fedora Server on lab boxes, internal build hosts and self-managed dev infrastructure, and they want Git hosting, CI and a container registry on the same machine. The catch is knowing which parts are safe, which parts need care, and which parts should never be trusted to a copy-paste from a forum thread. A GitLab instance holds source code, secrets and deploy keys. Treat it with the same suspicion you would give a database server.

This guide walks through a complete installation of GitLab Community Edition on Fedora 44 using the Linux package (Omnibus). It covers preparation, the repository workaround, HTTPS, firewalld, SELinux, resource tuning, backups and the errors that actually show up in the field. Commands assume a fresh Fedora Server install with root or sudo access. Fedora 44 is a young release, so check each step against your own system before you rely on it. Where something is a judgment call rather than documented behavior, it says so.

Before You Touch the Server

Understand the support situation

Omnibus GitLab bundles its own PostgreSQL, Redis, Nginx, Gitaly, Puma and Sidekiq under /opt/gitlab. That bundling is why it can work on a distribution GitLab never tested. The package carries most of its own dependencies, so the host OS mostly supplies the kernel, systemd, glibc and a few base libraries.

Mostly. Fedora moves forward every six months, and a new release can change glibc, OpenSSL, crypto policies or systemd behavior in ways an EL-built package never saw. A GitLab upgrade or a Fedora upgrade can therefore break things without warning, and nobody at GitLab will take your support ticket. For a personal lab or an internal team server, that risk is acceptable. For a revenue-critical production system, AlmaLinux or Rocky Linux is the safer base, and the commands in this guide transfer to them nearly unchanged.

Decide this up front. Retrofitting a platform choice after two years of merge requests is miserable.

Hardware sizing that matches reality

GitLab’s documented single-node baseline is 8 vCPU and 16 GB of RAM, and 8 GB is the floor for smaller setups. For memory-constrained boxes, GitLab documents a minimum of 2 GB RAM plus 1 GB swap, with 20 GB of storage and fast random I/O, SSD preferred. The package itself needs roughly 2.5 GB of disk, but repositories, artifacts, uploads and logs grow far beyond that.

A practical way to read those numbers:

Scenario CPU RAM Disk
Personal or tiny team (under 5 users) 2 vCPU 4 GB (tuned) 40 GB SSD
Small team (5 to 50 users) 4 vCPU 8 GB 100 GB NVMe
Active org (up to roughly 1,000 users) 8 vCPU 16 GB 500 GB+ NVMe

The 4 GB tier works, but expect to tune it (see the performance section). Below that, the experience is poor. Gitaly, Puma, Sidekiq and PostgreSQL all want memory at once, and the first big clone or CI burst will trigger the OOM killer.

One detail is easy to miss. GitLab’s documentation advises disabling swap where possible on larger installs, because swapping under load hurts performance badly, yet it also recommends a modest swap on small machines. These pieces of advice apply to different tiers. On an 8 GB box, a small swap file as a safety net is reasonable. On 32 GB or more, provision enough RAM that swap never matters.

Domain, DNS and ports

Pick the hostname now, for example git.example.com. Create an A record (and AAAA if you have IPv6) pointing to the server before installation. Let’s Encrypt validation will fail otherwise, and changing external_url later forces a reconfigure and breaks every existing clone URL.

You will need these ports reachable:

  • 80/tcp for HTTP and ACME challenges
  • 443/tcp for HTTPS
  • 22/tcp for SSH administration (and possibly Git over SSH)

A frequent conflict: GitLab’s Git-over-SSH wants port 22, but your admin sshd already owns it. Solve this with a different admin SSH port, or by setting gitlab_rails['gitlab_shell_ssh_port'] later. Decide now.

Preparing Fedora 44

Update the system and set the hostname

Start clean.

sudo dnf upgrade --refresh -y
sudo reboot

The reboot matters if a new kernel was installed. Many people skip it, then wonder why uname -r disagrees with what is installed when they debug something later.

Set a fully qualified hostname:

sudo hostnamectl set-hostname git.example.com
hostnamectl

Confirm the name resolves to the right address:

getent hosts git.example.com

Install base dependencies

sudo dnf install -y curl policycoreutils openssh-server perl chrony
sudo systemctl enable --now sshd chronyd

Time sync deserves a line of justification. Clock drift breaks TLS validation, two-factor authentication codes (TOTP), and CI job signatures. Chrony is the default on Fedora, but verify it is actually synchronized:

chronyc tracking
timedatectl

Many older guides also install Postfix for outgoing mail. Today a relay through a transactional provider (SES, Mailgun, your own SMTP relay) is better. Running a local MTA on a git server tends to end with your notifications landing in spam. Skip Postfix and configure SMTP inside GitLab later.

Open the firewall

Fedora Server uses firewalld by default.

sudo systemctl enable --now firewalld
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --reload
sudo firewall-cmd --list-all

If the server sits on a public network, consider restricting SSH to your office or VPN range with a rich rule rather than leaving it open to the world:

sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.0/24" service name="ssh" accept'

Then remove the generic ssh service rule once you are sure the rich rule works from your location. Test from a second session before closing the first. Everyone locks themselves out exactly once.

Keep SELinux enforcing

Check the state:

getenforce

You should see Enforcing. Do not disable it just because a guide from 2018 said so. The Omnibus package ships its own SELinux handling for the standard paths, and the policycoreutils package supplies the tools it calls during reconfigure. If you later relocate data directories, you must label them correctly, which is covered in the troubleshooting section.

Adding the GitLab Repository

Why the official script may fail on Fedora

GitLab’s convenience script (script.rpm.sh) detects your OS and builds a repo definition. On a distribution it does not recognize, it can abort with an unsupported OS message. That is the script being cautious, not the package being broken. Piping a remote script into a root shell is also something many admins avoid on principle.

The alternative is to define the repository by hand and point it at the Enterprise Linux build. A Fedora-oriented walkthrough from Atlantic.Net used exactly this approach, with a gitlab-ce.repo file referencing the el/8 path. EL 8 is long past its usefulness for new installs, so use the el/9 path for current builds. Verify that path exists for the version you intend to install, and if GitLab has published el/10 packages by the time you read this, evaluate that too. Check the package listing at packages.gitlab.com first.

Create the repo file

sudo tee /etc/yum.repos.d/gitlab_gitlab-ce.repo > /dev/null <<'EOF'
[gitlab_gitlab-ce]
name=gitlab_gitlab-ce
baseurl=https://packages.gitlab.com/gitlab/gitlab-ce/el/9/$basearch
repo_gpgcheck=1
gpgcheck=1
enabled=1
gpgkey=https://packages.gitlab.com/gitlab/gitlab-ce/gpgkey
       https://packages.gitlab.com/gitlab/gitlab-ce/gpgkey/gitlab-gitlab-ce-3D645A26AB9FBD22.pub.gpg
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
metadata_expire=300

[gitlab_gitlab-ce-source]
name=gitlab_gitlab-ce-source
baseurl=https://packages.gitlab.com/gitlab/gitlab-ce/el/9/SRPMS
repo_gpgcheck=1
gpgcheck=1
enabled=0
gpgkey=https://packages.gitlab.com/gitlab/gitlab-ce/gpgkey
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
metadata_expire=300
EOF

Refresh metadata and check that the repository is visible:

sudo dnf clean all
sudo dnf makecache
dnf list --showduplicates gitlab-ce | tail -n 5

If makecache complains about GPG on the repository metadata, see the troubleshooting section. Fedora’s crypto policy and newer rpm signature handling occasionally object to older key material.

Choose a version deliberately

The default dnf install gitlab-ce pulls the newest release. For a first install that is usually fine. For anything with users, pin the version so that a routine dnf upgrade cannot jump you across a required upgrade stop:

sudo dnf install -y 'dnf-command(versionlock)'

After install you will lock it:

sudo dnf versionlock add gitlab-ce

GitLab upgrades follow required stops for database migrations. Skipping those is how you end up restoring from backup at 2 AM. Always read the upgrade path tool on GitLab’s docs before moving more than one minor version.

Installing and Configuring GitLab

Install with the external URL set

Setting EXTERNAL_URL as an environment variable during install writes it into the config automatically and runs the first reconfigure:

sudo EXTERNAL_URL="https://git.example.com" dnf install -y gitlab-ce

Using an https:// URL tells Omnibus to request a Let’s Encrypt certificate at the end of the reconfigure, as long as DNS and port 80 are reachable from the internet. If your server is internal-only, Let’s Encrypt cannot validate it. Use http:// for the initial install and configure your own certificate afterwards.

Expect the install to take several minutes. The package is large and the first reconfigure runs Chef to generate configs, initialize PostgreSQL and run database migrations. Do not interrupt it. If your SSH session is flaky, run it inside tmux.

Retrieve the initial root password

sudo cat /etc/gitlab/initial_root_password

That file is deleted automatically after 24 hours, a design choice that has bitten more than one person who installed on Friday and logged in Monday. Log in as root, change the password immediately, and enable two-factor authentication on the admin account.

If the file is gone, reset the password from the console:

sudo gitlab-rake "gitlab:password:reset[root]"

The settings file that matters

Everything lives in /etc/gitlab/gitlab.rb. Back it up before editing:

sudo cp -a /etc/gitlab/gitlab.rb /etc/gitlab/gitlab.rb.$(date +%F)
sudo vi /etc/gitlab/gitlab.rb

A sensible baseline for a public-facing instance:

external_url 'https://git.example.com'

letsencrypt['enable'] = true
letsencrypt['contact_emails'] = ['sysadmin@example.com']
letsencrypt['auto_renew'] = true

nginx['redirect_http_to_https'] = true
nginx['ssl_protocols'] = "TLSv1.2 TLSv1.3"

gitlab_rails['gitlab_email_from'] = 'gitlab@example.com'
gitlab_rails['gitlab_email_display_name'] = 'Example GitLab'

gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = "smtp.example.com"
gitlab_rails['smtp_port'] = 587
gitlab_rails['smtp_user_name'] = "smtp-user"
gitlab_rails['smtp_password'] = "CHANGE_ME"
gitlab_rails['smtp_domain'] = "example.com"
gitlab_rails['smtp_authentication'] = "login"
gitlab_rails['smtp_enable_starttls_auto'] = true

gitlab_rails['backup_keep_time'] = 604800
gitlab_rails['time_zone'] = 'Asia/Jakarta'

Apply it:

sudo gitlab-ctl reconfigure
sudo gitlab-ctl restart

Two habits worth building. First, never edit files under /var/opt/gitlab/ by hand; the next reconfigure overwrites them. Second, treat gitlab.rb as code and keep it in a private configuration repository, with secrets pulled in from a vault or environment rather than committed in plain text.

Verify that everything is healthy

sudo gitlab-ctl status
sudo gitlab-rake gitlab:check SANITIZE=true
sudo gitlab-rake gitlab:env:info

Each service should report run. The gitlab:check task flags missing SSH host keys, bad permissions and uninitialized hooks. Read its output rather than just glancing for green.

Then test from the outside:

curl -I https://git.example.com/users/sign_in
openssl s_client -connect git.example.com:443 -servername git.example.com </dev/null 2>/dev/null | openssl x509 -noout -dates -issuer

Hardening the Instance

Lock down sign-ups and authentication

On a fresh install, open registration is enabled. Anyone who finds the URL can create an account. Go to Admin Area, Settings, General, Sign-up restrictions, and either disable sign-up or require admin approval. Do this before you announce the URL.

Further admin-side settings worth setting on almost every deployment:

  • Require two-factor authentication for all users with a short grace period.
  • Set a minimum password length of at least 12.
  • Restrict allowed SSH key types and minimum key sizes.
  • Disable unused features such as Auto DevOps if you do not use it.
  • Set default project visibility to private.

SSH hygiene

If you run both admin SSH and GitLab’s Git-over-SSH, keep them clearly separated. Move the system sshd to a high port, or bind it to a management interface only:

sudo vi /etc/ssh/sshd_config.d/10-hardening.conf
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3

Validate before reloading, because a typo here costs you the box:

sudo sshd -t && sudo systemctl reload sshd

Fail2ban and rate limiting

GitLab has built-in Rack Attack rate limiting for authentication endpoints, and it deserves to be turned on in Admin Area, Settings, Network. On top of that, Fail2ban against SSH and the Nginx access log catches brute-force noise earlier. Install it from the Fedora repos:

sudo dnf install -y fail2ban
sudo systemctl enable --now fail2ban

If you sit behind a reverse proxy or CDN, set the real client IP header properly (nginx['real_ip_header'] and nginx['real_ip_trusted_addresses']), otherwise every failed login looks like it came from the proxy and you will ban your own load balancer.

Permissions and secrets

Two files deserve special protection: /etc/gitlab/gitlab-secrets.json and /etc/gitlab/gitlab.rb. The secrets file holds the keys used to encrypt database columns, CI variables and two-factor seeds. Lose it, and a database backup restores into an instance that cannot decrypt half its own data.

sudo ls -l /etc/gitlab/
sudo chmod 600 /etc/gitlab/gitlab-secrets.json

Copy it somewhere safe and separate from your data backups. Putting both in the same bucket defeats the purpose.

Performance and Resource Tuning

Know where the memory goes

On a stock install, the heavy hitters are Puma (web), Sidekiq (background jobs), Gitaly (Git operations), PostgreSQL and Prometheus monitoring. GitLab’s documentation notes that each Puma worker is capped at 1.2 GB by default and that a node must never run fewer than two Puma workers in clustered mode. For example, it suggests two workers for 2 cores and 8 GB, and four for 4 cores and 8 GB. Sidekiq starts above 200 MB and can climb past 1 GB on busy servers.

Watch it live:

sudo gitlab-ctl top
free -h
sudo smem -rtk 2>/dev/null | head -n 15

A tuned config for small servers

For a 4 GB machine, GitLab publishes a constrained-environment recipe: single-process Puma, lower Sidekiq concurrency, monitoring off, and a jemalloc setting that releases memory sooner. In gitlab.rb:

puma['worker_processes'] = 0
sidekiq['concurrency'] = 10
prometheus_monitoring['enable'] = false
gitlab_rails['env'] = {
  'MALLOC_CONF' => 'dirty_decay_ms:1000,muzzy_decay_ms:1000'
}

Disabling Prometheus means losing built-in metrics. If you still want visibility, run node_exporter and a lightweight external monitor. Reconfigure afterwards:

sudo gitlab-ctl reconfigure

Scaling up on bigger hosts

On an 8 vCPU, 16 GB host, leave Puma in clustered mode and size workers to your cores. A common starting formula is cores multiplied by roughly 1.5 plus one, though memory limits it. Seven workers at 1.2 GB each already eats about 8.4 GB before Sidekiq or PostgreSQL has loaded. So size on memory first, cores second.

puma['worker_processes'] = 4
puma['per_worker_max_memory_mb'] = 1024
sidekiq['concurrency'] = 20
postgresql['shared_buffers'] = "2GB"

PostgreSQL’s shared_buffers is typically set near a quarter of RAM allotted to the database. Treat these numbers as a starting point and measure.

Disk I/O and storage layout

Git workloads punish slow disks with many small random reads, which is why GitLab’s own docs rank SSD above HDD and call out good random I/O. Practical suggestions:

  • Put /var/opt/gitlab/git-data on NVMe or fast SSD storage.
  • Use XFS (the Fedora Server default) or ext4 with noatime on the data mount.
  • Keep backups on a separate physical volume or remote storage.
  • Monitor inode usage as well as free space, since repositories create enormous numbers of small objects.
df -hT /var/opt/gitlab
df -i /var/opt/gitlab
iostat -xz 5 3

Relocating the data directory is a common need. Here is the clean way:

git_data_dirs({
  "default" => { "path" => "/mnt/gitdata" }
})

On SELinux systems, label the new path or Gitaly will fail with permission errors even though Unix permissions look right (see below).

Network considerations

Large clones and CI artifact transfers can saturate a modest uplink. A few options help: enable Git protocol v2 (default in modern Git), place a CDN or caching proxy in front of static assets, and use shallow clones in CI (GIT_DEPTH: 20). If you have runners on the same LAN, they bypass the public path entirely, which cuts bandwidth cost noticeably.

Tune kernel parameters only when you have evidence. A reasonable baseline for busy servers:

sudo tee /etc/sysctl.d/90-gitlab.conf > /dev/null <<'EOF'
vm.swappiness = 10
net.core.somaxconn = 1024
fs.file-max = 500000
EOF
sudo sysctl --system

Backups, Upgrades and Runners

A backup routine that gets tested

GitLab’s built-in backup task produces a tar archive of the database, repositories, uploads and more:

sudo gitlab-backup create STRATEGY=copy

The STRATEGY=copy option copies data first to avoid tar complaining about files changing mid-read on a live system. Remember that this archive does not include gitlab.rb or gitlab-secrets.json, so back those up separately:

sudo tar czf /root/gitlab-config-$(date +%F).tgz -C /etc gitlab

Schedule it with a systemd timer, or cron if you prefer:

sudo crontab -e
15 2 * * * /usr/bin/gitlab-backup create CRON=1 STRATEGY=copy
30 2 * * * tar czf /root/gitlab-config-$(date +\%F).tgz -C /etc gitlab

Ship both off the box (rsync, restic or object storage). The real lesson from production incidents is simple: an untested backup is a hope, not a backup. Restore into a scratch VM every quarter and confirm you can log in and clone a repository.

Upgrading without drama

sudo gitlab-backup create STRATEGY=copy
sudo dnf versionlock delete gitlab-ce
sudo dnf install -y gitlab-ce-<target-version>
sudo dnf versionlock add gitlab-ce
sudo gitlab-ctl status

Check required upgrade stops and background migrations first. GitLab will complain loudly if you skip ahead, but only after the package has already been swapped. Upgrading Fedora itself (for example, 44 to 45 someday) deserves the same caution. Snapshot the VM, back up, and read the GitLab release notes before the OS jump.

Adding a GitLab Runner

CI jobs should not execute on the GitLab server. Use a separate host or VM:

curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh" -o runner-repo.sh
less runner-repo.sh

Read it, then run it, or define the repo manually as shown earlier. Register with a token created in the GitLab UI. Prefer the Docker or Podman executor for isolation, and keep runner hosts disposable. A compromised build job on the same machine as your repositories is a nightmare scenario.

Troubleshooting Common Problems

Reconfigure hangs at “ruby_block[wait for redis service socket]”

Usually Redis failed to start. Check:

sudo gitlab-ctl tail redis
sudo gitlab-ctl status

Common causes are a stale socket, a full disk, or vm.overcommit_memory warnings on small hosts. Fix the cause, then:

sudo gitlab-ctl reconfigure

502 Bad Gateway right after boot or restart

Puma takes a minute or two to warm up, especially on a small machine. Wait, then look at the logs:

sudo gitlab-ctl tail puma
sudo gitlab-ctl tail nginx

If Puma is being killed, dmesg -T | grep -i -E 'killed process|out of memory' will confirm an OOM kill. Reduce workers or add RAM. GitLab’s docs warn that around 4 GB is a bare minimum and that you may see 500 errors and odd failures under that line.

Let’s Encrypt fails to issue a certificate

Typical culprits: DNS not yet propagated, port 80 blocked by firewalld or an upstream security group, or a CDN proxying the challenge path. Confirm:

dig +short git.example.com
curl -I http://git.example.com/.well-known/acme-challenge/test
sudo firewall-cmd --list-services

Then force a retry:

sudo gitlab-ctl renew-le-certs

Remember that Let’s Encrypt rate-limits repeated failures. Fix the cause before hammering it.

SELinux denials after moving data

Look for AVC messages:

sudo ausearch -m avc -ts recent
sudo journalctl -t setroubleshoot --since "1 hour ago"

For a relocated Git data directory:

sudo semanage fcontext -a -t var_t "/mnt/gitdata(/.*)?"
sudo restorecon -Rv /mnt/gitdata
sudo gitlab-ctl reconfigure

If semanage is missing, install policycoreutils-python-utils. Resist the urge to set permissive mode as a shortcut. It hides the problem and removes a real protection layer.

GPG or signature errors from dnf

If metadata checks fail with a key or signature error, re-import the key and clean the cache:

sudo rpm --import https://packages.gitlab.com/gitlab/gitlab-ce/gpgkey
sudo dnf clean all && sudo dnf makecache

If the failure persists on Fedora’s stricter crypto policy, check update-crypto-policies --show. Loosening the policy system-wide is a bad trade; prefer investigating the specific key. As a last resort, setting repo_gpgcheck=0 on that single repo (keeping gpgcheck=1 for the packages themselves) is an acceptable compromise that many admins tolerate temporarily.

Emails are not arriving

sudo gitlab-rails console
Notify.test_email('you@example.com', 'Test', 'Hello').deliver_now

Errors there point straight at SMTP authentication, TLS or relay restrictions. Also check SPF, DKIM and DMARC on your sending domain, or Gmail will quietly discard everything.

Git push over SSH fails with “Permission denied (publickey)”

Confirm the key was added to the right user, and that /var/opt/gitlab/.ssh/authorized_keys is being managed by GitLab (not by an admin mistakenly editing it). Test verbosely:

ssh -vT git@git.example.com

If your admin sshd sits on port 22 and GitLab’s shell is also expected there, you have the port conflict mentioned earlier.

Monitoring, Logs and Daily Operations

Useful commands that earn their keep:

sudo gitlab-ctl status
sudo gitlab-ctl tail
sudo gitlab-ctl restart puma
sudo gitlab-rake gitlab:check
sudo gitlab-rake gitlab:doctor:secrets
sudo journalctl -u gitlab-runsvdir -f

Rotate and cap logs. Omnibus ships logrotate-style settings; review logging['svlogd_size'] and logrotate_frequency so that /var/log/gitlab cannot silently consume the root volume. Alert on disk usage above 80 percent, on failing backups, on certificate expiry within 14 days, and on Sidekiq queue latency. A growing Sidekiq queue is often the earliest sign that something upstream is wrong.

For uptime checks, probe the readiness endpoint rather than the home page:

curl -fsS https://git.example.com/-/readiness

The /-/health and /-/liveness endpoints are intended for load balancers and monitoring. Whitelist your monitoring IPs in gitlab_rails['monitoring_whitelist'] so the checks are not blocked.

r00t is an experienced Linux enthusiast and technical writer with a passion for open-source software. With years of hands-on experience in various Linux distributions, r00t has developed a deep understanding of the Linux ecosystem and its powerful tools. He holds certifications in SCE and has contributed to several open-source projects. r00t is dedicated to sharing her knowledge and expertise through well-researched and informative articles, helping others navigate the world of Linux with confidence.

Related Posts