Skip to content

How to Deploy GitLab on a VPS

Updated Sep 2026

Self-host GitLab CE on your own VPS — the real Omnibus package install, Let's Encrypt HTTPS, hardening, and a backup/restore you actually test, sized to GitLab's documented 16 GB baseline.

Before you start
  • A VPS that clears GitLab's own documented baseline — 16 GB RAM / 8 vCPU for a single-node install (8 GB is an explicit memory-constrained fallback, not a target)
  • A fresh Ubuntu 24.04 or 26.04 server with root/sudo SSH access
  • A domain you can point at the server — GitLab's own Let's Encrypt automation needs it before install
  • 40 GB+ of disk — repositories, the container/package registries, and CI artifacts all land on the same volume as GitLab itself
Need a box for this guide? Kamatera's free tier lets you spin one up now.Start free on Kamatera → (opens in new tab)

What GitLab is

GitLab CE is the whole DevOps platform in one install: repositories, merge requests, issues, a built-in CI/CD runner fleet, container and package registries, and Pages. It's open core — everything outside the ee/ and jh/ directories is MIT, the paid-tier code inside them is not — and rated 4 / 5 to deploy, which is honest: this is by far the heaviest app in this catalogue.

The reason is the install itself. GitLab ships as an Omnibus package, not a single binary or a light container — one gitlab-ce package bundles PostgreSQL, Redis, Gitaly, Puma, Sidekiq, and its own NGINX, configured from one file (/etc/gitlab/gitlab.rb) and applied with one command (gitlab-ctl reconfigure). That's also why this guide installs the native Linux package rather than a container: upstream explicitly steers self-managed single-node installs there, and reserves the Docker image mainly for quick trials (it warns against running that image under Kubernetes — use the Helm chart or Operator there instead).

This guide is the "how to install it" half. For which VPS provider, tier, and monthly cost actually clears GitLab's requirements, see Best VPS for GitLab — that page owns the provider comparison and isn't repeated here.

Server sizing — GitLab sets the floor, not you

Unlike most of this catalogue, sizing GitLab isn't really a judgment call. GitLab's own single-node requirements documentation lists 16 GB RAM and 8 vCPU as the baseline, and separately publishes 8 GB as workable only in a memory-constrained setup — a fallback, not a plan. Undersizing doesn't fail cleanly: it shows up first as slow CI pipelines and a growing Sidekiq job backlog, then as OOM kills once Puma, Sidekiq, and Gitaly are all competing for the same memory.

Disk matters as much as RAM here. Repositories, LFS objects, the container and package registries, and CI artifacts all accumulate on the same volume, and none of them shrink on their own — start at 40 GB minimum and favor a plan you can grow later over one that's merely RAM-heavy. First boot is also slow (the package is large and gitlab-ctl reconfigure does real work on first run), so budget several minutes before the install answers.

The provider/tier math for clearing that 16 GB floor at a sane price is covered in Best VPS for GitLab; this guide assumes you've already picked a box that qualifies.

Prepare the server

GitLab's Omnibus package needs a handful of dependencies and its own firewall ports — there's no Docker layer to prepare here, since the package runs directly on the host:

sudo apt update
sudo apt install -y curl openssh-server ca-certificates tzdata perl

If the install later prompts for a mail transport agent (Postfix), either configure it for "Internet Site" with your domain, or accept the default and configure SMTP inside GitLab afterward — GitLab needs outbound mail for password resets and notification emails either way.

Open only what GitLab actually needs. Git-over-SSH uses the host's own sshd on port 22 — Omnibus doesn't run a separate SSH server the way a Dockerized forge does, so there's no extra port to publish:

sudo ufw allow OpenSSH
sudo ufw allow 80
sudo ufw allow 443
sudo ufw enable
sudo ufw status verbose
Where to host itaffiliate disclosure
Contaborun it on
4 vCPU · 8 GB RAM · 100 GB SSD · $4.95/mo
Get Contabo (opens in new tab)
OVHcloudalso works on
2 vCPU · 4 GB RAM · 40 GB SSD · $4.54/mo
Get OVHcloud (opens in new tab)
Hetzner Cloudalso works on
2 vCPU · 4 GB RAM · 80 GB SSD · $23.59/mo
Get Hetzner Cloud (opens in new tab)

Install GitLab

Add GitLab's official Community Edition package repository, then install the package with EXTERNAL_URL set to your real domain over https — setting it with the https scheme up front is what triggers GitLab's built-in Let's Encrypt automation during install, so there's no separate certificate step later:

curl --location "https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh" | sudo bash
sudo EXTERNAL_URL="https://gitlab.example.com" apt install gitlab-ce

The package install runs gitlab-ctl reconfigure for you on first install, which brings up PostgreSQL, Redis, Gitaly, Puma, Sidekiq, and NGINX in turn — this is the slow part, expect several minutes on a freshly sized VPS.

Read the initial root password before it's gone. GitLab writes a one-time password to /etc/gitlab/initial_root_password:

sudo cat /etc/gitlab/initial_root_password

That file is deleted on the first restart after 24 hours have passed since the first reconfigure — read it now, log in, and rotate the password (next section) rather than depending on it surviving.

Pin a version for anything beyond a first test. apt install gitlab-ce without a version pulls the latest release; for a reproducible install, check what's available and pin it:

apt-cache policy gitlab-ce
sudo EXTERNAL_URL="https://gitlab.example.com" apt install gitlab-ce=<version>

HTTPS + domain

Create an A record for gitlab.example.com pointing at the server's public IP before running the install above — GitLab's Let's Encrypt automation needs to answer an HTTP-01 challenge on port 80 during gitlab-ctl reconfigure, and that fails silently if DNS hasn't propagated yet.

Because EXTERNAL_URL was set with https://, Let's Encrypt is already live: GitLab checked out a certificate during install and renews it automatically (around the 4th of each month, well before the 90-day expiry). To add a contact email for renewal notices, edit /etc/gitlab/gitlab.rb:

letsencrypt['contact_emails'] = ['you@example.com']

Then apply it:

sudo gitlab-ctl reconfigure

If you'd rather bring your own certificate (an internal CA, or a wildcard you already manage) instead of GitLab's built-in Let's Encrypt, set letsencrypt['enable'] = false and point nginx['ssl_certificate'] / nginx['ssl_certificate_key'] at your files in the same block, then reconfigure.

First login and hardening

Load https://gitlab.example.com and sign in as root with the password from /etc/gitlab/initial_root_password. Do these immediately, in order:

  1. Rotate the root password under User Settings → Password. The initial one lived in a plaintext file on the server; treat it as burned.
  2. Turn on two-factor auth for the root account under User Settings → Account, and require it instance-wide later under Admin Area → Settings → General → Sign-up restrictions.
  3. Disable public sign-up. Admin Area → Settings → General → Sign-up restrictions → turn off "Sign-up enabled". An internet-facing GitLab with open registration collects spam accounts fast. To set it from config instead, add to gitlab.rb:
    gitlab_rails['gitlab_signup_enabled'] = false
    and sudo gitlab-ctl reconfigure.
  4. Set the default project visibility to private under Admin Area → Settings → Visibility and access controls, unless you're deliberately running a public forge.
  5. Configure SMTP (Admin Area → Settings, or gitlab_rails['smtp_*'] in gitlab.rb) if you skipped Postfix earlier — password resets and CI notifications depend on outbound mail working.

Backups

Omnibus ships its own backup command, which produces a single consistent archive of the application data:

sudo gitlab-backup create

That lands in /var/opt/gitlab/backups by default (configurable via gitlab_rails['backup_path'] in gitlab.rb) as <timestamp>_<version>_gitlab_backup.tar.

The backup command deliberately excludes secrets. Back these up separately, and store them somewhere other than next to the data backup — they're the decryption keys for it:

sudo cp /etc/gitlab/gitlab-secrets.json /etc/gitlab/gitlab.rb /path/to/off-box-backup/

Ship the tarball and the two config files off the VPS on a schedule — object storage, another machine, anywhere the server dying doesn't take them too. Restore it once into a throwaway instance so you find out now that the backup is complete, not during an outage:

sudo gitlab-ctl stop puma
sudo gitlab-ctl stop sidekiq
sudo gitlab-backup restore BACKUP=<timestamp_and_version_from_filename>
sudo gitlab-ctl start
sudo gitlab-rake gitlab:check SANITIZE=true

(BACKUP= takes just the ID prefix of the filename — drop the trailing _gitlab_backup.tar.)

Upgrades

Take a backup first (previous section) — GitLab's database migrations are one-way. Then upgrade the package like any other:

sudo apt update
sudo apt install gitlab-ce

The package's post-install hook runs gitlab-ctl reconfigure and any pending migrations automatically. GitLab enforces upgrade paths across major versions — jumping too many minor/major releases at once can skip a required intermediate step, so check the official upgrade path guidance for your current version before a big jump rather than assuming any newer package installs cleanly on top of an old one.

Troubleshooting

The site 502s right after install. First boot is slow — Puma, Sidekiq, and Gitaly are still coming up behind gitlab-ctl reconfigure. Check sudo gitlab-ctl status and give it a few more minutes before assuming something is broken.

gitlab-ctl reconfigure fails partway through. Re-run it — Chef (the tool reconfigure runs on) is idempotent, so a second run usually finishes past whatever failed. If it keeps failing, read the tail of the output it prints; it names the exact resource that errored.

Let's Encrypt didn't issue a certificate. Confirm the domain's A record resolves to the server before reconfiguring, and that ports 80/443 are open in ufw — the HTTP-01 challenge needs both. Re-run sudo gitlab-ctl reconfigure once DNS is correct.

/etc/gitlab/initial_root_password is gone and you never logged in. It's deleted on the first restart after 24 hours have passed since the first reconfigure. Reset the root password directly instead:

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

Notification emails never arrive. SMTP isn't configured. Check gitlab_rails['smtp_enable'] and the related settings in gitlab.rb, or finish the Postfix prompt from the dependency install.

Verification + next steps

You're done when you can: load https://gitlab.example.com with a valid certificate, sign in with the rotated root password and 2FA on, confirm sign-up is disabled in a private window, push and pull a test repository over both HTTPS and SSH, and produce a gitlab-backup create archive you have restored at least once into a throwaway instance.

From there, the natural next steps are registering a GitLab Runner for CI/CD, enabling the container registry for your own images, and deciding default project visibility for anything you add later. If you're weighing GitLab against a lighter forge before committing a 16 GB box to it, the GitLab vs Gitea comparison covers the trade-off. For the ranked host picks that actually clear GitLab's footprint, see Best VPS for GitLab.

Next steps

How to self-host GitLab CE →More self-hosted code hosting tools →Best VPS for GitLab →Automatic HTTPS with Caddy →Run Claude Code with Ollama on Your Own VPS →Deploy Coolify on a VPS →How to Deploy Actual Budget on a VPS →How to Deploy AnythingLLM on a VPS →How to Deploy Appwrite on a VPS →How to Deploy Audiobookshelf on a VPS →How to Deploy Authelia on a VPS →How to Deploy authentik on a VPS →How to Deploy Baserow on a VPS →How to Deploy Beszel on a VPS →How to Deploy Bitwarden on a VPS →How to Deploy BookStack on a VPS →How to Deploy CapRover on a VPS →How to Deploy Checkmate on a VPS →How to Deploy Directus on a VPS →How to Deploy docker-mailserver on a VPS →How to Deploy Docmost on a VPS →How to Deploy Dokku on a VPS →How to Deploy Dokploy on a VPS →How to Deploy Firefly III on a VPS →How to Deploy Forgejo on a VPS →How to Deploy Gatus on a VPS →How to Deploy Ghostfolio on a VPS →How to Deploy Gitea on a VPS →How to Deploy GlitchTip on a VPS →How to Deploy Grafana on a VPS →How to Deploy Graylog on a VPS →How to Deploy Headscale on a VPS →How to Deploy Healthchecks on a VPS →How to Deploy Home Assistant on a VPS →How to Deploy Immich on a VPS →How to Deploy Jan on a VPS →How to Deploy Jellyfin on a VPS →How to Deploy Karakeep on a VPS →How to Deploy Keycloak on a VPS →How to Deploy Leantime on a VPS →How to Deploy LibreChat on a VPS →How to Deploy Linkwarden on a VPS →How to Deploy LocalAI on a VPS →How to Deploy Mailcow on a VPS →How to Deploy Mailu on a VPS →How to Deploy Matomo on a VPS →How to Deploy Mattermost on a VPS →How to Deploy Meilisearch on a VPS →How to Deploy Memos on a VPS →How to Deploy n8n on a VPS →How to Deploy Navidrome on a VPS →How to Deploy NetBird on a VPS →How to Deploy Netdata on a VPS →How to Deploy Nextcloud on a VPS →How to Deploy Next.js to a VPS →How to Deploy Nginx Proxy Manager on a VPS →How to Deploy NocoDB on a VPS →How to Deploy ntfy on a VPS →How to Deploy Ollama on a VPS →How to Deploy Open WebUI on a VPS →How to Deploy OpenHands on a VPS →How to Deploy OpenObserve on a VPS →How to Deploy OpenProject on a VPS →How to Deploy Outline on a VPS →How to Deploy Pangolin on a VPS →How to Deploy Paperless-ngx on a VPS →How to Deploy Passbolt on a VPS →How to Deploy Plane on a VPS →How to Deploy Plausible Analytics on a VPS →How to Deploy Pocket ID on a VPS →How to Deploy PocketBase on a VPS →How to Deploy Prometheus on a VPS →How to Deploy Psono on a VPS →How to Deploy Radarr on a VPS →How to Deploy Rocket.Chat on a VPS →How to Deploy SigNoz on a VPS →How to Deploy Sonarr on a VPS →How to Deploy Stalwart on a VPS →How to Deploy Stirling-PDF on a VPS →How to Deploy Supabase on a VPS →How to Deploy Synapse on a VPS →How to Deploy Taiga on a VPS →How to Deploy TeamPass on a VPS →How to Deploy Tinyauth on a VPS →How to Deploy Traefik on a VPS →How to Deploy Trilium on a VPS →How to Deploy Twenty CRM on a VPS →How to Deploy Umami on a VPS →How to Deploy Uptime Kuma on a VPS →How to Deploy Vaultwarden on a VPS →How to Deploy Vikunja on a VPS →How to Deploy wg-easy on a VPS →How to Deploy Wiki.js on a VPS →How to Deploy Zabbix on a VPS →How to Deploy Zitadel on a VPS →How to Deploy Zulip on a VPS →Docker & Compose on Ubuntu 26.04 →Building AI Workflows with n8n →Install Open WebUI with Ollama →Adding AI-Powered Insights to Plausible Analytics →Building AI-Powered Apps with Supabase and pgvector →

We use analytics cookies (Google Analytics, PostHog) to see which guides are useful. No ad networks, no cross-site tracking. See our privacy policy.