Configure GitLab container registry with SSL certificates and security hardening

Intermediate 45 min Apr 02, 2026 268 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Set up GitLab's integrated Docker registry with SSL/TLS encryption, security headers, and production-ready hardening for secure container image storage and distribution.

Prerequisites

  • GitLab CE installed
  • Domain with DNS configured
  • Root or sudo access
  • Docker installed

What this solves

GitLab's container registry provides secure Docker image storage integrated with your CI/CD pipelines. This tutorial configures the registry with SSL encryption, security headers, and access controls for production use.

Step-by-step configuration

Verify GitLab installation

Ensure you have a working GitLab CE installation before configuring the container registry.

sudo gitlab-ctl status
sudo gitlab-ctl reconfigure

Install SSL certificate dependencies

Install packages needed for SSL certificate management and Docker registry configuration.

sudo apt update
sudo apt install -y certbot openssl docker.io
sudo dnf update -y
sudo dnf install -y certbot openssl docker podman-docker

Generate SSL certificate

Create an SSL certificate for your registry subdomain. Replace example.com with your domain.

sudo certbot certonly --standalone \
  -d registry.example.com \
  --email admin@example.com \
  --agree-tos --non-interactive
Note: Ensure DNS points registry.example.com to your GitLab server and port 80 is accessible for Let's Encrypt validation.

Create registry storage directory

Set up the directory structure for container registry storage with proper permissions.

sudo mkdir -p /var/opt/gitlab/gitlab-rails/shared/registry
sudo chown git:git /var/opt/gitlab/gitlab-rails/shared/registry
sudo chmod 755 /var/opt/gitlab/gitlab-rails/shared/registry

Configure GitLab registry settings

Edit the GitLab configuration file to enable the container registry with SSL and security settings.

# Enable container registry
registry_external_url 'https://registry.example.com:5050'

Registry storage settings

gitlab_rails['registry_enabled'] = true gitlab_rails['registry_host'] = "registry.example.com" gitlab_rails['registry_port'] = "5050" gitlab_rails['registry_path'] = "/var/opt/gitlab/gitlab-rails/shared/registry"

SSL certificate configuration

registry_nginx['ssl_certificate'] = "/etc/letsencrypt/live/registry.example.com/fullchain.pem" registry_nginx['ssl_certificate_key'] = "/etc/letsencrypt/live/registry.example.com/privkey.pem" registry_nginx['ssl_protocols'] = "TLSv1.2 TLSv1.3" registry_nginx['ssl_ciphers'] = "ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384"

Security headers

registry_nginx['custom_nginx_config'] = "include /etc/gitlab/registry_security_headers.conf;"

Registry storage cleanup

registry['storage_delete_enabled'] = true registry['compatibility_schema1_enabled'] = false

Access control

registry['notifications'] = [{ 'name' => 'webhook', 'url' => 'https://registry.example.com/hook', 'headers' => { 'Authorization' => ['Bearer token123'] } }]

Create security headers configuration

Add security headers to protect against common web vulnerabilities.

# Security headers for GitLab registry
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'" always;

Registry-specific headers

add_header Docker-Content-Digest $upstream_http_docker_content_digest always; add_header Docker-Distribution-API-Version registry/2.0 always;

Configure firewall rules

Open the required ports for the container registry with specific access controls.

sudo ufw allow 5050/tcp comment 'GitLab Registry HTTPS'
sudo ufw reload
sudo firewall-cmd --permanent --add-port=5050/tcp
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="0.0.0.0/0" port protocol="tcp" port="5050" accept'
sudo firewall-cmd --reload

Set certificate permissions

Configure proper permissions for SSL certificates so GitLab can read them.

sudo chown root:gitlab-registry /etc/letsencrypt/live/registry.example.com/fullchain.pem
sudo chown root:gitlab-registry /etc/letsencrypt/live/registry.example.com/privkey.pem
sudo chmod 640 /etc/letsencrypt/live/registry.example.com/fullchain.pem
sudo chmod 640 /etc/letsencrypt/live/registry.example.com/privkey.pem
Never use chmod 777. It gives every user on the system full access to your SSL certificates. Use specific user/group ownership and minimal permissions.

Configure registry authentication token

Set up JWT token authentication for secure registry access.

sudo openssl rand -base64 32 | sudo tee /var/opt/gitlab/registry-auth.key
sudo chown git:git /var/opt/gitlab/registry-auth.key
sudo chmod 600 /var/opt/gitlab/registry-auth.key

Add authentication configuration

Configure JWT authentication settings in the GitLab configuration.

# Add JWT authentication settings
registry['auth_token_realm'] = "https://registry.example.com/jwt/auth"
registry['auth_token_service'] = "container_registry"
registry['auth_token_issuer'] = "gitlab-issuer"
registry['auth_token_rootcertbundle'] = "/var/opt/gitlab/registry-auth.key"

Health check configuration

registry['health_storagedriver_enabled'] = true registry['health_storagedriver_interval'] = '10s' registry['health_storagedriver_threshold'] = 3

Apply configuration changes

Reconfigure GitLab to apply all registry settings and restart services.

sudo gitlab-ctl reconfigure
sudo gitlab-ctl restart registry
sudo gitlab-ctl status

Configure automatic certificate renewal

Set up automatic SSL certificate renewal with post-renewal hooks.

sudo crontab -e

Add this line to renew certificates monthly:

0 3 1   /usr/bin/certbot renew --post-hook "gitlab-ctl restart registry" --quiet

Configure Docker client authentication

Login to GitLab registry

Test Docker client authentication using GitLab credentials or deploy tokens.

docker login registry.example.com:5050

Enter GitLab username and password or deploy token

Create GitLab deploy token

Generate a deploy token for CI/CD pipeline access to the registry.

# In GitLab UI: Project > Settings > Repository > Deploy Tokens

Create token with read_registry and write_registry scopes

Use token for Docker login:

docker login registry.example.com:5050 -u -p

Test registry functionality

Push test image

Test the registry by pushing and pulling a container image.

# Tag and push an image
docker pull alpine:latest
docker tag alpine:latest registry.example.com:5050/myproject/alpine:latest
docker push registry.example.com:5050/myproject/alpine:latest

Pull test image

Verify the registry by pulling the uploaded image.

docker rmi registry.example.com:5050/myproject/alpine:latest
docker pull registry.example.com:5050/myproject/alpine:latest

Verify your setup

# Check registry service status
sudo gitlab-ctl status registry

Verify SSL certificate

openssl s_client -connect registry.example.com:5050 -servername registry.example.com

Check registry API

curl -k https://registry.example.com:5050/v2/

Test Docker registry connectivity

docker info | grep -A 10 "Registry Mirrors"

Check registry logs

sudo gitlab-ctl tail registry

Security hardening

Configure registry resource limits

Set resource limits to prevent denial of service attacks.

# Add resource limits
registry['middleware'] = {
  'registry' => [{
    'name' => 'cloudfront',
    'options' => {
      'baseurl' => 'https://registry.example.com:5050',
      'privatekey' => '/var/opt/gitlab/registry-auth.key',
      'keypairid' => 'registry-key'
    }
  }]
}

Rate limiting

registry_nginx['rate_limiting_zone'] = '$binary_remote_addr zone=registry:10m rate=10r/m' registry_nginx['rate_limiting_burst'] = 20

Enable audit logging

Configure comprehensive audit logging for registry access.

# Enable audit logging
registry['log_level'] = 'info'
registry['log_formatter'] = 'json'
registry['log_fields'] = {
  'service' => 'registry',
  'environment' => 'production'
}

Apply security configuration

Reconfigure GitLab to apply security hardening settings.

sudo gitlab-ctl reconfigure
sudo gitlab-ctl restart registry

Common issues

SymptomCauseFix
SSL certificate errorsIncorrect certificate path or permissionsVerify certificate paths and run sudo chown root:gitlab-registry /etc/letsencrypt/live/*/
Docker login failsAuthentication configuration missingCheck JWT token settings and run sudo gitlab-ctl reconfigure
Push/pull timeoutsFirewall blocking registry portEnsure port 5050 is open: sudo ufw allow 5050/tcp
Registry not startingStorage directory permissionsFix ownership: sudo chown git:git /var/opt/gitlab/gitlab-rails/shared/registry
403 Forbidden errorsMissing deploy tokens or permissionsCreate deploy token with registry scopes in GitLab project settings
Certificate renewal failsGitLab process locks certificate filesAdd post-hook: --post-hook "gitlab-ctl restart registry"

Next steps

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle managed devops services for businesses that depend on uptime. From initial setup to ongoing operations.