Guide
GitLab SSL certificate expired
Git fetch and the GitLab UI fail when the Omnibus nginx TLS certificate expires. Renew Let’s Encrypt or custom certs, run gitlab-ctl reconfigure, and verify live HTTPS.
Updated May 9, 2026 996 words
Developers see fatal: unable to access https://gitlab: SSL certificate problem: certificate has expired while SSH git still works—gitlab certificate expired is almost always the Omnibus nginx ssl_certificate listener, not the Gitaly internal network.
GitLab Omnibus terminates HTTPS on bundled nginx. gitlab-ctl renew-le-certs exists for Let’s Encrypt, but many installs use corporate SAN certs placed under /etc/gitlab/ssl/. Certbot on the host may renew elsewhere while GitLab still serves the old gitlab.crt.
CI runners cloning over HTTPS share the same certificate as the web UI. Container registries at registry.gitlab.example.com may use a separate cert configured in registry_nginx—renewing only the main external_url leaves docker push failing.
First response
Read external_url from /etc/gitlab/gitlab.rb, list PEM paths in that stanza, and openssl the live hostname from outside the VLAN:
sudo grep -E '^external_url|ssl_certificate' /etc/gitlab/gitlab.rb
openssl x509 -in /etc/gitlab/ssl/gitlab.example.com.crt -noout -dates 2>/dev/null
echo | openssl s_client -connect gitlab.example.com:443 -servername gitlab.example.com 2>/dev/null | openssl x509 -noout -dates
Note GitLab version and whether Geo secondaries use their own certs. Attach output to the incident before reconfigure.
What this error means
git and curl validate the certificate presented on external_url. Expired notAfter fails before HTTP routes reach Rails. Internal gitlab-kas, Workhorse, and Gitaly TLS may use different certs—do not assume one renewal fixes every component.
Pages, Container Registry, and Mattermost toggles add vhosts. Each can reference distinct ssl_certificate keys in gitlab.rb. Partial expiry explains “UI loads, registry push fails.”
Geo and runners are clients too: secondary nodes pulling from primary over HTTPS need valid certs on both ends.
Large monorepos amplify outage impact: every clone, LFS fetch, and CI artifact upload fails PKIX before auth, so SSO success on an unrelated IdP never helps if git uses HTTPS remotes.
Kubernetes agent and Flux integrations that poll the GitLab API over HTTPS share the same listener cert as humans browsing the UI. Platform automation stalls in parallel with developer complaints.
Package registry and dependency proxy hostnames introduced in newer releases may point at vhosts operators forgot to list on the corporate SAN request. Renewal of the main GitLab FQDN alone leaves package clients failing while issue boards still load on cached browser sessions.
Common causes
Manual cert drop replaced .crt without updating intermediate chain GitLab nginx expects. Let’s Encrypt renewal succeeded but gitlab-ctl hup nginx never ran after cron.
Wildcards renewed on a central appliance; GitLab VM still mounts last year’s disk snapshot PEM. Kubernetes Helm chart updated tls-secret in one cluster; DR GitLab still serves stale material.
gitlab-ctl reconfigure failed mid-run leaving old symlinks. Operators ran reconfigure only on primary in HA Geo without secondaries.
Sidekiq and Rails background jobs do not present the user-facing cert, but web hooks and integrations delivered via HTTPS callbacks fail when remote systems validate your external_url cert before accepting delivery retries.
Omnibus backup restores that reinstate ssl directory trees from months-old archives reintroduce expired material silently after unrelated DR testing. Compare live openssl serial to intended renewal file serial before closing.
Load balancers terminating TLS in front of Omnibus may still serve expired edge certs while nginx on the VM already holds renewed PEM—external developers see failure until the edge team reloads.
Confirm from the outside
OpenSSL
echo | openssl s_client -connect gitlab.example.com:443 -servername gitlab.example.com 2>/dev/null | openssl x509 -noout -dates -subject -issuer
curl (matches git error)
curl -vI https://gitlab.example.com/users/sign_in 2>&1 | grep -Ei 'expire|SSL certificate'
Registry (if enabled)
echo | openssl s_client -connect registry.gitlab.example.com:443 -servername registry.gitlab.example.com 2>/dev/null | openssl x509 -noout -dates
How to read the output
Live notAfter must match the file you intend to serve. Serial should change after renewal. Registry hostname needs its own SAN unless you use a wildcard covering it.
Probe Pages custom domains separately when customers publish user sites on distinct DNS names—Omnibus may terminate those on dedicated vhosts with independent cert paths.
Compare disk vs live when LB exists
When HAProxy or cloud LB fronts GitLab, openssl the public name and compare with cert on the Omnibus node. Mismatch tells you which team owns the remaining renewal step.
Fix checklist
- Obtain renewed cert + full chain for external_url and registry hostnames.
- Place PEMs in paths referenced by gitlab.rb (or update paths to new files).
- Run gitlab-ctl reconfigure (and gitlab-ctl restart nginx if needed).
- For Let’s Encrypt: gitlab-ctl renew-le-certs then verify cron in /etc/cron.d/.
- HA/Geo: repeat on each node serving TLS to users or primaries.
- Test git clone https://, UI login, docker login registry.
- Update CI GIT_SSL_NO_VERIFY temp flags—remove after fix.
- Archive openssl before/after in ticket; link certbot renew failed if ACME broke.
Schedule reconfigure during low push windows but expect nginx reload to drop long polling connections briefly. Communicate to release managers running trunk-based merges.
Validate Geo replication lag after primary cert fix; secondaries may need the same PEM if they terminate TLS locally for regional users.
If registry_nginx uses different paths, update both cert pairs before inviting wide docker login tests—partial fixes strand only container teams and prolongs split-brain ownership.
Refresh Let's Encrypt managed Omnibus installs by confirming acme challenge DNS or HTTP reachability after renewal failures earlier in the month.
When to use automation
Git is down for everyone when gitlab certificate expired hits—treat external_url as tier-zero. Monitor live TLS on GitLab, Pages, and registry DNS names separately.
Use /check on each hostname developers type. Pair with peer CI tooling guides (Jenkins SSL, Jira/Confluence) so platform teams share one renewal calendar.
Frequently asked questions
Omnibus vs Helm?
Helm uses ingress TLS secrets; reconfigure maps to updating the secret and reloading ingress. Paths differ; openssl the public hostname stays the same test.
Will reconfigure restart everything?
Reconfigure reloads nginx and may restart dependent services—schedule a maintenance window on busy instances.
Custom CA for internal GitLab?
Internal users need trust store distribution; expiry still applies. Public Internet users need publicly trusted certs on external_url.
Mattermost bundled?
If enabled, check mattermost_nginx SSL paths separately from main GitLab vhost.
See what the server presents right now
Free live certificate check — no signup.