Background: When GitHub Isn’t Enough for an Internal Team
Our team has 5 developers and 2 sysadmins — small, but large enough that GitHub Free was becoming a real problem. CI minutes on private repos ran out faster than expected. Some internal projects simply couldn’t have their code pushed to the cloud. And when we tried self-hosting GitLab Runner, build times dropped noticeably — no more depending on upstream bandwidth to GitHub’s servers.
The reason I chose Fedora Server is pretty personal — I’d been using Fedora desktop for 2 years and was comfortable with it. Packages update faster than CentOS or Rocky Linux, the kernel is newer, and SELinux policies get patched more frequently. GitLab CE runs great on Fedora, as long as you don’t do what most guides suggest: disabling SELinux and firewalld to make setup quicker.
Minimum specs: 4GB RAM, 2 CPUs, 20GB disk. But that’s “minimum” in the literal sense — GitLab at idle already consumes around 2.5–3GB RAM. Planning to run GitLab Runner on the same machine? Get 8GB to be safe.
Installing GitLab CE
Preparing the System
Update the system and install the required dependencies first:
sudo dnf update -y
sudo dnf install -y curl policycoreutils openssh-server openssh-clients postfix
sudo systemctl enable --now sshd postfix
Installing GitLab CE from the Official Repository
GitLab provides an automatic repo setup script, after which you install the package normally via DNF:
curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.rpm.sh | sudo bash
sudo EXTERNAL_URL="http://gitlab.yourdomain.com" dnf install -y gitlab-ce
Replace gitlab.yourdomain.com with your actual domain or IP. For internal use with a static IP, http://192.168.1.100 works fine. After installation, GitLab automatically runs gitlab-ctl reconfigure for the first time — this takes about 3–5 minutes, which is completely normal.
Detailed Configuration
Handling SELinux — Don’t Disable It
This is the biggest difference from most guides I’ve read. Many articles tell you to run setenforce 0 or set SELINUX=disabled in /etc/selinux/config for a quick fix. I don’t do that — disabling SELinux on a production server throws away an entire layer of protection just to save a few minutes of setup. GitLab CE actually only needs a few booleans enabled:
# Allow GitLab to make network connections (needed for webhooks, email)
sudo setsebool -P httpd_can_network_connect on
sudo setsebool -P httpd_can_network_relay on
# Fix file context for GitLab data directory
sudo semanage fcontext -a -t gitlab_shell_t "/var/opt/gitlab(/.*)?"
sudo restorecon -Rv /var/opt/gitlab/
# Check if SELinux is blocking anything
sudo ausearch -m avc -ts recent | grep gitlab
If there are still SELinux errors after enabling the booleans, read the audit log and create a custom policy:
sudo ausearch -m avc -ts recent | audit2allow -M gitlab-custom
sudo semodule -i gitlab-custom.pp
This approach is much safer than disabling SELinux — it only permits the exact operations GitLab needs. As a bonus, reading the audit logs gives you insight into what GitLab is actually doing under the hood, which is invaluable when troubleshooting later.
Configuring firewalld
GitLab needs HTTP (80) and HTTPS (443). SSH cloning shares port 22 with the system SSH, so no additional port needs to be opened:
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
# Verify the configuration
sudo firewall-cmd --list-all
If GitLab runs on a custom port (e.g., 8080), add the corresponding rule:
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload
Tuning gitlab.rb
The main configuration file is /etc/gitlab/gitlab.rb. Here are the settings I use in production:
# /etc/gitlab/gitlab.rb
# Main URL — must be correct, use HTTPS if you have SSL
external_url 'https://gitlab.yourdomain.com'
# SMTP configuration (Gmail example)
gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = "smtp.gmail.com"
gitlab_rails['smtp_port'] = 587
gitlab_rails['smtp_user_name'] = "[email protected]"
gitlab_rails['smtp_password'] = "app-password-here"
gitlab_rails['smtp_domain'] = "gmail.com"
gitlab_rails['smtp_authentication'] = "login"
gitlab_rails['smtp_enable_starttls_auto'] = true
# Limit RAM for Puma web server
puma['worker_processes'] = 2
puma['min_threads'] = 1
puma['max_threads'] = 4
# Reduce Sidekiq workers to save RAM
sidekiq['concurrency'] = 10
# Keep backups for 7 days
gitlab_rails['backup_keep_time'] = 604800
Apply the changes:
sudo gitlab-ctl reconfigure
Enabling HTTPS with Let’s Encrypt
GitLab CE has Let’s Encrypt built in. Add this to gitlab.rb:
letsencrypt['enable'] = true
letsencrypt['contact_emails'] = ['[email protected]']
letsencrypt['auto_renew'] = true
letsencrypt['auto_renew_hour'] = 3
letsencrypt['auto_renew_day_of_month'] = "*/7"
sudo gitlab-ctl reconfigure
sudo gitlab-ctl renew-le-certs
Verification and Monitoring
First Login
Get the temporary root password (valid for 24 hours):
sudo cat /etc/gitlab/initial_root_password
Log in at http://your-gitlab-url with the root account and that password, then change it immediately. Don’t leave it until tomorrow.
Checking Service Status
sudo gitlab-ctl status
Normal output looks like this — all services should be in the run state:
run: alertmanager: (pid 1234) 120s
run: gitaly: (pid 1236) 120s
run: nginx: (pid 1240) 120s
run: postgresql: (pid 1242) 120s
run: redis: (pid 1244) 120s
run: sidekiq: (pid 1246) 120s
run: puma: (pid 1248) 120s
If any service is down, restart it individually:
sudo gitlab-ctl restart puma
sudo gitlab-ctl restart sidekiq
Health Check and Database Verification
# Run a full health check
sudo gitlab-rake gitlab:check
# Test email sending (run in Rails console)
sudo gitlab-rails console
# In the console, type:
Notify.test_email('[email protected]', 'GitLab Test', 'Test email').deliver_now
Monitoring with Prometheus and Grafana
GitLab has Prometheus and Grafana built in. Enable Grafana in gitlab.rb:
grafana['enable'] = true
grafana['http_port'] = 3000
sudo gitlab-ctl reconfigure
# Access: http://your-gitlab-url/-/grafana
The Grafana dashboard shows CPU/RAM usage per service (Puma, Sidekiq, Gitaly, PostgreSQL), along with request rate and latency. I keep this tab open during large deployments to spot bottlenecks immediately — it saves a significant amount of debugging time.
Automated Backups
# Create a manual backup
sudo gitlab-backup create
# View created backups
ls -lh /var/opt/gitlab/backups/
Add to root’s crontab to run automated backups at 2 AM:
sudo crontab -e
# Add the following line:
0 2 * * * /opt/gitlab/bin/gitlab-backup create CRON=1
Common Issues After 6 Months in Production
- 502 Bad Gateway: Usually Puma hasn’t finished starting. Wait 1–2 minutes or run
sudo gitlab-ctl restart puma. - SELinux blocking git operations: Run
sudo ausearch -m avc | grep gitand create a policy as described above. - Disk fills up fast: GitLab CI artifacts and the container registry eat disk space quickly. Set
artifacts_expire_inin each project’s CI settings and clean up the registry periodically withsudo gitlab-ctl registry-garbage-collect. - Abnormally high RAM: Sidekiq sometimes develops a memory leak after running for many days — add
sidekiq['enable_sidekiq_cluster'] = falseor schedule periodic restarts via cron.
Add a simple alert when disk is nearly full:
# Add to crontab
*/30 * * * * df -h /var/opt/gitlab | awk 'NR==2 {gsub(/%/,"",$5); if ($5+0 > 80) print "GitLab disk usage: " $5 "%"}' | grep -q "%" && echo "Disk alert" | mail -s "GitLab Disk High" [email protected]
After 6 months in production, GitLab CE on Fedora has been more stable than I initially expected. SELinux was the most frustrating part in the first week — constantly discovering new operations that needed policy updates. But once that phase was done, I didn’t have to touch it for months. The biggest payoff is still the internal CI/CD with GitLab Runner: build times are noticeably shorter, no worrying about running out of minutes, and no dependency on upstream bandwidth.

