In 2026, GitLab CE's hardware footprint is significant: the Omnibus bundle wants 4 GB RAM minimum, 8 GB recommended, plus a multi-core CPU. If you want issues, MRs, and CI/CD and your team is small, Gitea or Forgejo with Gitea Actions is dramatically lighter. GitLab is only the right choice when you specifically need its enterprise-grade features (Auto DevOps, dependency scanning, portfolio management).
Prerequisites
- A Debian 11 or 12 server with at least 4 GB RAM and 2 vCPUs. Less and GitLab will sporadically OOM.
- A subdomain pointed at that server's IP:
gitlab.yourdomain.com. - An existing OpenLiteSpeed setup with a working Let's Encrypt cert, per my OpenLiteSpeed full-stack guide.
Step 1: Add GitLab's apt repository
curl -fsSL https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash
The script adds the repo, the signing key, and refreshes the apt cache. Review before executing if you want — it's short and readable.
Step 2: Install GitLab CE
sudo apt install gitlab-ce
This is a ~700 MB download and a multi-minute install because the Omnibus package ships its entire dependency tree. The postinst script will warn that no external_url has been configured — that's next.
Step 3: Configure the external URL and internal port
GitLab's bundled nginx needs to know (a) what URL users will reach it at, and (b) what port to listen on. We'll put it on a high internal port and reverse-proxy in front.
sudo nano /etc/gitlab/gitlab.rb
Find and set:
# The public URL — must match what you serve at your reverse proxy.
external_url 'https://gitlab.yourdomain.com'
# Bind the bundled nginx to localhost only, on a non-standard port.
nginx['listen_addresses'] = ['127.0.0.1']
nginx['listen_port'] = 8929
nginx['listen_https'] = false
# GitLab's own Let's Encrypt integration — disable, since we handle certs in OpenLiteSpeed.
letsencrypt['enable'] = false
# Tell GitLab the reverse proxy already handles HTTPS.
gitlab_workhorse['trusted_cidrs'] = ['127.0.0.1/32']
# Optional: enable the built-in container registry, behind a separate subdomain
# registry_external_url 'https://registry.yourdomain.com'
Apply the config:
sudo gitlab-ctl reconfigure
The first reconfigure takes 3–5 minutes because it's generating secrets, seeding the database, and compiling assets. Subsequent runs are much faster.
Verify GitLab is up on its internal port:
sudo gitlab-ctl status
curl -I http://127.0.0.1:8929/
# HTTP/1.1 302 Found — redirecting to /users/sign_in
Step 4: Extend the Let's Encrypt cert to cover the subdomain
If your existing cert covers only yourdomain.com and www.yourdomain.com, re-run certbot with the subdomain added:
sudo certbot certonly --webroot \
-w /var/www/yourdomain.com/html -d yourdomain.com -d www.yourdomain.com \
-w /var/www/gitlab/html -d gitlab.yourdomain.com
The command reuses the existing cert's account; you end up with a single cert covering all three hostnames via Subject Alternative Names.
If you've set Strict-Transport-Security: ... includeSubDomains on your main domain and the browser remembers it, browsers will refuse to load the new subdomain over HTTP even for the certbot webroot challenge. Temporarily drop includeSubDomains from your security-headers Context, clear your browser cache, run certbot, re-add it. Or use the DNS-01 challenge instead of HTTP-01, which doesn't need the web server at all.
Step 5: Configure OpenLiteSpeed to proxy
In WebAdmin:
- Create a new Virtual Host:
gitlab.yourdomain.com, root/var/www/gitlab, config file auto-generated. Graceful restart. - Under the new vhost → External App → Add. Type: Web Server. Name:
gitlab. Address:http://127.0.0.1:8929. Max Conns:100. Connection Keepalive Timeout:60. - Context → Add. Type: Proxy. URI:
/. Web Server:gitlab. Accessible: Yes. - General tab: Document Root
$VH_ROOT/html. - SSL tab: point at the cert/key paths from certbot (
/etc/letsencrypt/live/yourdomain.com/fullchain.pemetc.). - Rewrite tab: enable, auto-load .htaccess off, add the HTTPS-redirect rule:
rewriteCond %{HTTPS} !on rewriteRule ^(.*)$ https://%{SERVER_NAME}%{REQUEST_URI} [R=301,L] - Under the HTTP and HTTPS listeners, add a Virtual Host Mapping for the new vhost: domain
gitlab.yourdomain.com, vhostgitlab.yourdomain.com. - Graceful restart.
Visit https://gitlab.yourdomain.com/ — you should see the GitLab sign-in page. First login is root; the initial password is in /etc/gitlab/initial_root_password. Change it immediately, then delete that file.
Step 6: Pass real client IPs through the proxy
Out of the box, GitLab will see 127.0.0.1 for every request (because that's the OpenLiteSpeed side of the proxy). To get real client IPs in the audit log:
# /etc/gitlab/gitlab.rb
nginx['real_ip_trusted_addresses'] = ['127.0.0.1']
nginx['real_ip_header'] = 'X-Forwarded-For'
nginx['real_ip_recursive'] = 'on'
And in the OpenLiteSpeed proxy Context, make sure Header Operations includes:
requestheader set X-Forwarded-For %{REMOTE_ADDR}
requestheader set X-Forwarded-Proto https
Then sudo gitlab-ctl reconfigure.
Runners for CI/CD
GitLab itself doesn't execute CI jobs; it schedules them onto runners. Install the runner (can be on the same machine or, better, on a separate box):
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash
sudo apt install gitlab-runner
sudo gitlab-runner register --url https://gitlab.yourdomain.com --token "REG_TOKEN"
# Pick 'docker' as the executor for most cases
Get REG_TOKEN from GitLab UI: Admin Area → CI/CD → Runners → New instance runner.
Backups
Omnibus ships a single-command backup:
sudo gitlab-backup create
Writes a timestamped tarball to /var/opt/gitlab/backups/. Schedule it nightly:
# /etc/cron.d/gitlab-backup
0 3 * * * root /usr/bin/gitlab-backup create SKIP=tar | tar -cf /var/backups/gitlab-$(date +\%F).tar -
Then ship those tarballs off-box with restic or rclone. Also back up /etc/gitlab/ separately — it contains secrets used to decrypt the data in the backup, and a backup without those secrets is useless.
Important paths to remember
/etc/gitlab/gitlab.rb— main configuration/var/log/gitlab/— logs, split by component (nginx, puma, sidekiq, postgres, etc.)/var/opt/gitlab/— state (repos, database, uploads, artifacts)/opt/gitlab/— the actual application code; don't touch