Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

The safest way to put a self-hosted GitLab instance behind NGINX is to choose one owner for the public web ports, make GitLab aware of the public HTTPS URL, and preserve the original host and forwarding headers on every request. Linux package installations use GitLab’s bundled NGINX by default. You can keep that generated configuration, or disable it and let an existing NGINX reverse proxy own ports 80 and 443.

The choice affects certificate termination, configuration ownership, client-IP handling, and whether Registry, Pages, and KAS require separate settings. The procedures below cover both layouts and the failures most often caused by an incorrect proxy scheme or missing headers.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choose who owns ports 80 and 443

There are two supported layouts. Decide this before editing files; running two independent NGINX instances on the same public port is not a workable configuration.

Decision point Bundled NGINX External NGINX
Public listener GitLab’s generated NGINX listens on the public address. Your existing NGINX listens on ports 80 and 443 and proxies to GitLab.
Configuration owner GitLab writes the server configuration from /etc/gitlab/gitlab.rb. You maintain the reverse-proxy server block; GitLab’s bundled listener is disabled.
TLS termination Usually occurs in the bundled NGINX. Usually occurs in the external NGINX or another load balancer.
Proxy headers Generated defaults are tied to external_url. Your server block must forward the public host, scheme, port, and client address.
Auxiliary services Registry, Pages, and KAS use their own NGINX configuration hashes. Each exposed auxiliary service needs its own proxy and redirect configuration.

Use bundled NGINX when

You want GitLab to generate and maintain the listener, certificate paths, and most of the application-specific routing. This is the default for Linux package installations and usually has fewer moving parts. It is also the simpler choice when GitLab is the only application on the host and no other service needs ports 80 or 443.

#1 Best Overall
Recipe Book to Write in Your Own Recipes, Spiral Blank Cookbook 150 Recipes
  • B5 Recipe Book: This hardcover blank recipe book measures 10''×7.3'', containing 150 blank pages, comes with 1 sheet of DIY category stickers, perfect for writing down your own recipes and organizing them in one place.
  • Practical Layout - 5 pages of conversion charts show you everything you'll ever need to know in the kitchen. The table of contents will help organize your personal favorite recipes. A back pocket is designed for loose items.
  • Easy to Use - This blank cookbook is made of high-quality paper with a beautiful hardcover for long-term use. The sturdy gold double-wire spiral binding makes writing easier and page turning smoother.
  • Make Your Own Recipes - This blank cookbook has up to 150 blank pages for you to write in your own recipes, with plenty of space to record ingredients and instructions. You can write down all the special tips for your family members.
  • Thoughtful Gift - A personalized recipe book makes a thoughtful gift for your children or newlyweds, and a great gift for housewarming, weddings, bridal showers, anniversaries, or any other occasion.

Use external NGINX when

You already operate a shared edge proxy, need one place for several applications, or must terminate TLS before traffic reaches GitLab. In this layout, disable the bundled listener explicitly and grant the external NGINX account the access GitLab requires. The external design gives you control over the public server block, but it also makes header correctness and reload procedures your responsibility.

Preflight checks

Before changing anything, record the public DNS name, the component that currently owns ports 80 and 443, and the point where certificates terminate. Confirm that the DNS name resolves to the intended edge address and that firewall rules permit the listener you plan to use. If another load balancer is already in front of NGINX, document which device adds each forwarding header and whether it sends ordinary HTTP or the PROXY protocol. This prevents a later configuration from trusting or overwriting the wrong hop.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Set GitLab’s canonical public URL first

In /etc/gitlab/gitlab.rb, set external_url to the exact URL users will enter, including https:// when HTTPS is public. GitLab uses this value to generate links, redirects, clone URLs, and default proxy behavior. A mismatch between the browser’s host or scheme and external_url is a common cause of redirect loops and failed CSRF checks.

Use one canonical hostname. Do not alternate between an IP address, an internal DNS name, and the public name during testing, because cookies and redirects are scoped to the host GitLab believes is canonical. If a separate Registry, Pages, or KAS hostname is exposed, document those names independently and configure their service-specific settings rather than trying to make the main URL cover every service.

After every change to gitlab.rb, apply it with:

sudo gitlab-ctl reconfigure

Do not treat a successful command as proof that the edge proxy is correct. Test a browser login, a repository clone over HTTPS, and an API request from outside the server. Also test the exact hostname users will use; a request made directly to an internal address can hide a bad Host header or an incorrect scheme.

Configure the bundled NGINX topology

1. Keep the generated listener enabled

Leave the bundled NGINX enabled (the default) and configure GitLab’s NGINX-related keys in gitlab.rb. If the server is directly internet-facing, configure the certificate and key paths supported by your GitLab package and ensure the firewall allows the public ports. Keep certificate files readable by the service account and use the package’s documented SSL procedure when replacing them.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

2. Reconfigure and verify listeners

  1. Edit /etc/gitlab/gitlab.rb and confirm external_url matches the public DNS name.
  2. Run sudo gitlab-ctl reconfigure.
  3. Check that the expected NGINX process owns the intended ports and that no separate web server is competing for them.
  4. Open the public URL, sign in, and verify that links remain on the same host and scheme.
  5. Clone a repository, upload a small file, and make an API request to exercise more than the login page.

In this topology, GitLab owns the generated server definition. Make persistent changes through the appropriate keys in gitlab.rb instead of editing generated files directly; a later reconfigure can replace generated content. Use the service-specific hashes for Registry, Pages, and KAS rather than assuming a setting under the main GitLab application will be inherited.

Configure an existing external NGINX

1. Disable GitLab’s bundled listener

In /etc/gitlab/gitlab.rb, set:

nginx['enable'] = false

Also configure the external web-server account used by your distribution. Debian and Ubuntu commonly use www-data; RHEL and CentOS commonly use nginx. GitLab’s package configuration exposes an external-user setting for this purpose; set it to the account that actually runs your NGINX worker processes, then run:

sudo gitlab-ctl reconfigure

The account must be able to reach the GitLab endpoint and any Unix socket or files involved in the package’s web-server integration. Confirm the account with your service manager instead of assuming the distribution default. If the worker account is wrong, NGINX can start successfully while failing to connect to the application or read a required socket.

2. Keep the public name in GitLab

Disabling the bundled listener does not change the canonical URL. Keep external_url set to the public hostname and scheme. GitLab still uses it when generating absolute URLs and validating requests. The internal backend can use a different address and port; that private hop should not leak into redirects or clone URLs.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

3. Create the external server block

Your NGINX server block should accept the public host, terminate TLS if NGINX is the TLS endpoint, and proxy every GitLab path to the internal endpoint used by your installation. The internal address and port are deployment-specific; obtain them from the package configuration or your local architecture rather than copying an address from a generic example.

server {
    listen 443 ssl;
    server_name gitlab.example.com;

    ssl_certificate     /path/to/your/certificate;
    ssl_certificate_key /path/to/your/private-key;

    location / {
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-Ssl on;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Port $server_port;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_http_version 1.1;
        proxy_pass http://gitlab_backend;
    }
}

upstream gitlab_backend {
    # Define the GitLab endpoint used by this installation.
}

The upstream block is intentionally not filled with a universal port: package layouts, sockets, containers, and separate internal proxies differ. The important requirement is that proxy_pass points to the endpoint that serves GitLab in your deployment. The same principle applies if the endpoint is a Unix socket or another local reverse proxy.

4. Add HTTP handling deliberately

If HTTP should redirect to HTTPS, create a separate port-80 server block that returns a permanent redirect to the same host. Do not create a second redirect in GitLab and NGINX that changes the scheme back and forth. If an upstream load balancer already performs the redirect, keep the NGINX behavior consistent with it and ensure the forwarded scheme still describes what the browser used.

5. Validate the external configuration before reload

Run your distribution’s NGINX syntax test command, inspect the selected server names, and confirm that the certificate and key paths are readable. Reload only after the test succeeds. Then make an external request and inspect the response headers and redirect location. A local request to the backend alone cannot verify that the public Host and HTTPS information survive the proxy.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Terminate HTTPS before GitLab

When a proxy or load balancer terminates TLS, GitLab’s internal listener must understand that the public request was HTTPS even though the hop from the proxy to GitLab is plain HTTP. GitLab documents these settings for the GitLab-side NGINX listener:

gitlab_rails['nginx']['listen_port'] = 80
gitlab_rails['nginx']['listen_https'] = false

Apply them with:

sudo gitlab-ctl reconfigure

Use this arrangement when GitLab’s NGINX is the internal HTTP listener behind the TLS terminator. If you have disabled the bundled listener entirely, configure the equivalent plain-HTTP backend in the component that actually serves GitLab; do not enable two listeners accidentally. TLS termination belongs in one clearly identified place, while the forwarded headers tell GitLab that the original client connection was secure.

Forward the headers GitLab uses for scheme and CSRF validation

GitLab needs the original host and request scheme, not merely the details of the internal proxy hop. At minimum, preserve Host, X-Forwarded-Ssl, X-Forwarded-For, and X-Forwarded-Port. Generated configurations also use X-Forwarded-Proto when external_url is HTTPS.

  • Host: keeps links, redirects, and cookie scope on the public hostname.
  • X-Forwarded-Proto: tells GitLab whether the browser used HTTP or HTTPS.
  • X-Forwarded-Ssl: explicitly indicates an SSL-origin request for deployments that rely on it.
  • X-Forwarded-Port: preserves the public port when the internal hop uses another port.
  • X-Forwarded-For: carries the original client chain for logging and access decisions.

Set these values consistently at the trusted proxy boundary. A later proxy should append to the client chain rather than replace it without a reason, and it should not claim HTTPS for an HTTP request that never used TLS. If several proxies are present, trace the headers hop by hop so you know which device is authoritative.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

If these values are absent or contradictory, symptoms can include a redirect to the wrong scheme, “The change you wanted was rejected,” or Can’t verify CSRF token authenticity Completed 422 Unprocessable. Check the request headers at the proxy boundary before changing Rails or application settings. GitLab specifically warns that incompatible proxy_set_header usage in custom server configuration can cause external-NGINX problems; use the supported proxy_set_headers setting in gitlab.rb when an override is required.

Handle trusted proxies and real client IPs

When another proxy sits in front of NGINX, GitLab must know which addresses are allowed to assert forwarded client information. Add the actual address ranges of those upstream proxies to gitlab_rails['nginx']['real_ip_trusted_addresses']. Use the ranges present in your network, not a broad private-network range chosen for convenience.

GitLab documents pairing the trusted-address list with:

Rank #2
Pipilo Press Blank Family Cookbook Recipe Binder - 6.5 x 8.2 in Hardcover Spiral Bound - 90 Ruled Pages with Memo Pocket - Write In Your Own Recipes & Meal Planning Notebook
  • Create a Culinary Legacy: The Pipilo Press Blank Family Cookbook Recipe Binder offers a timeless platform for chronicling beloved recipes. With its hardcover and spiral binding, this recipe book to write in your own recipes ensures durability and ease of use for generations
  • Thoughtful Gifting Choice: This book for writing recipes is an ideal present for various occasions, adding a personal touch to celebrations. Its elegant design makes it perfect for moms cookbook DIY projects or anyone passionate about preserving family traditions
  • Durable Design Features: Constructed with a 300gsm cardstock cover and matte lamination, this cookbook you can write in ensures longevity. Its spiral-bound format provides convenient access to pages, making it a practical tool for everyday use
  • Organized Recipe Storage: The included memo pocket offers a space to store handwritten notes or recipe cards, enhancing its functionality. This feature transforms the recipe book for moms into a sentimental family memento
  • Ample Writing Space: Measuring 6.5 x 8.2 inches, this blank spiral family recipe cookbook offers plenty of room for detailed notes. It is perfect for jotting down ingredients, preparation methods, and serving suggestions for a complete culinary record
gitlab_rails['nginx']['real_ip_header'] = 'X-Forwarded-For'
gitlab_rails['nginx']['real_ip_recursive'] = 'on'

real_ip_recursive is useful when a chain contains multiple known proxies. Trusting an address that is not controlled by you allows clients to forge their apparent source IP, while failing to trust the real proxy leaves logs and rate-limit decisions pointed at the proxy itself. Review this list whenever a load balancer is added, removed, or moved to a new network range.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

PROXY protocol deployments

If HAProxy or another upstream sends the PROXY protocol, enable gitlab_rails['nginx']['proxy_protocol'] = true and configure the trusted upstream addresses. A listener configured for PROXY protocol expects that protocol on every connection. Health checks, monitoring probes, and direct clients must therefore use matching PROXY-protocol behavior; ordinary HTTP probes will fail against such a listener. If only some traffic can speak PROXY protocol, use a separate listener rather than enabling it on a shared endpoint.

Configure Registry, Pages, and KAS independently

The main GitLab application’s NGINX settings do not automatically propagate to auxiliary services. Use the dedicated hashes:

  • registry['nginx'] for the container registry.
  • gitlab_pages['nginx'] for GitLab Pages.
  • gitlab_kas['nginx'] for the Kubernetes Agent Server.

Give each service the hostname, certificate, listener, and proxy behavior appropriate to its endpoint. If both GitLab and Registry must redirect HTTP to HTTPS, configure the redirect for both; changing only the main application leaves the registry on a different policy. Test each hostname separately, because a working GitLab login does not prove that a Registry token exchange or Pages request is routed correctly.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Add custom NGINX server blocks without breaking GitLab’s generated routes

GitLab supports a custom NGINX configuration directory through nginx['custom_nginx_config']. Point that setting at a directory you control, create the documented sites-available and sites-enabled structure inside it, and enable a site with a symlink from the former to the latter. Keep custom files separate from generated files so a reconfigure does not overwrite local changes.

After changing a custom server block, reload the bundled NGINX gracefully with:

sudo gitlab-ctl hup nginx

Restart NGINX only when a reload cannot apply the change. Avoid redefining GitLab’s generated root and /assets locations. For additional locations, GitLab notes that you may need proxy_cache off, proxy_http_version 1.1, and:

proxy_pass http://gitlab-workhorse;

Preserving the generated GitLab Workhorse route prevents static assets and application requests from being sent through an incompatible handler. A custom location that appears harmless can otherwise change caching, connection behavior, or the handler receiving Rails requests.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Apply changes safely

  1. Save a copy of /etc/gitlab/gitlab.rb and the active external NGINX configuration.
  2. Check syntax in the external NGINX configuration with your distribution’s NGINX test command before reloading.
  3. Run sudo gitlab-ctl reconfigure after changing GitLab package settings.
  4. Use sudo gitlab-ctl hup nginx for supported custom-configuration reloads.
  5. Test the public URL, login, a repository clone, an upload, and any Registry, Pages, or KAS hostname separately.
  6. Inspect GitLab and NGINX logs while testing so you can distinguish a proxy rejection from an application error.

Make one class of change at a time. First establish the canonical URL and listener ownership, then validate TLS and headers, and only afterward add custom locations or auxiliary services. This sequence makes it easier to identify whether a failure comes from DNS, port ownership, TLS, proxy routing, or GitLab application validation.

Certificate replacement deserves separate attention: GitLab notes that changing certificate files without changing gitlab.rb may not be acted on by a reconfigure alone. Follow the certificate deployment and reload procedure for the component that terminates TLS, then verify the served certificate from an external client. If an external NGINX terminates TLS, reloading the bundled GitLab NGINX will not replace the certificate users see.

Troubleshoot the common failures

Symptom Likely cause What to check
Browser repeatedly switches between HTTP and HTTPS GitLab sees the internal HTTP hop but not the public HTTPS scheme. Confirm X-Forwarded-Proto, X-Forwarded-Ssl, X-Forwarded-Port, and the TLS-termination settings.
“The change you wanted was rejected” or 422 CSRF error Host, scheme, or forwarded headers are missing or inconsistent. Inspect the external NGINX proxy_set_header directives before changing Rails settings.
Redirects use an internal hostname or port external_url does not match the public URL, or the Host header is rewritten. Correct external_url and pass the original Host.
Logs show the proxy address instead of the user The proxy is not trusted for real-IP processing, or the wrong header is selected. Set the actual upstream addresses, real_ip_header, and, where needed, recursive processing.
Health checks fail after enabling PROXY protocol The listener now expects PROXY-protocol bytes. Make health checks speak the protocol or use a separate listener that does not require it.
Registry redirects differently from GitLab Registry has its own NGINX hash and was not configured. Configure registry['nginx'] and its external proxy explicitly.
Custom location breaks assets or streaming requests A generated root or Workhorse location was replaced. Remove the duplicate location and preserve the documented cache, HTTP/1.1, and Workhorse directives.
NGINX starts but GitLab returns a gateway error The external worker account or backend endpoint is wrong. Verify the configured external user, socket or port permissions, and the deployment-specific proxy_pass target.
Only one auxiliary hostname works Settings were placed in the main application hash and were not inherited. Configure the Registry, Pages, or KAS hash and proxy server block for that service.

Frequently asked questions

Should I use GitLab’s bundled NGINX or my existing reverse proxy?

Use the bundled service when GitLab is the only application and you want package-managed configuration. Use an external NGINX when a shared edge already owns ports 80 and 443 or when TLS must terminate before GitLab. Neither choice is inherently more secure; the important requirement is one clear listener owner and correct forwarding information.

Can I leave the bundled NGINX enabled behind an external proxy?

Yes, it can serve as an internal backend listener, but it must not compete with the external service for the public ports. If the external design replaces GitLab’s listener, set nginx['enable'] = false. If the bundled service remains internal, use the documented plain-HTTP listener settings and point the external proxy to that internal endpoint.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Why does a 422 CSRF error appear only when I log in?

Login and other state-changing requests validate the host, scheme, and CSRF token more strictly than a simple page fetch. A missing or contradictory Host, X-Forwarded-Proto, X-Forwarded-Ssl, or X-Forwarded-Port header can make GitLab reject an otherwise reachable request. Inspect headers at the proxy before changing application code.

Do Registry, Pages, and KAS inherit the main NGINX settings?

No. Each service has its own NGINX configuration hash and may use a separate hostname, listener, certificate, or redirect policy. Configure and test every exposed service independently.

When should I use real-IP recursion?

Use real_ip_recursive = 'on' when multiple known proxies add to the forwarding chain. Pair it with a precise trusted-address list and the correct real-IP header. Do not enable trust for an address range that is not controlled by your infrastructure.

What changes require reconfigure, and what changes require a reload?

Changes in /etc/gitlab/gitlab.rb require sudo gitlab-ctl reconfigure. Changes to supported custom bundled-NGINX files can be applied with sudo gitlab-ctl hup nginx. A certificate file replacement may require the certificate deployment and reload procedure even when gitlab.rb is unchanged.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Is there a broader NGINX reference worth keeping nearby?

NGINX Cookbook, 3rd Edition from O’Reilly is a general NGINX configuration and reverse-proxy reference. It is broader than GitLab, so use GitLab’s package settings and service-specific documentation for GitLab behavior, then use the book for general NGINX patterns.

Final deployment checklist

  • One clearly identified component owns public ports 80 and 443.
  • external_url exactly matches the public hostname and scheme.
  • The bundled listener is either intentionally enabled or explicitly disabled.
  • The external NGINX account is configured and has the required access.
  • TLS termination and GitLab’s internal listener use the same intended scheme.
  • Host, forwarded scheme, forwarded SSL, forwarded port, and client-IP headers are preserved.
  • Trusted proxy addresses match the real network path.
  • PROXY protocol is enabled only when every client of that listener supports it.
  • Registry, Pages, and KAS have separate settings where they are exposed.
  • Custom locations preserve GitLab’s generated root, assets, and Workhorse behavior.
  • Changes were reconfigured, reloaded, and tested through the public URL.

Frequently Asked Questions

What is the safest first step when GitLab is behind NGINX?

Choose the single component that owns ports 80 and 443, then set GitLab’s external_url to the exact public hostname and scheme before adjusting proxy details.

How do I fix a GitLab redirect loop behind TLS termination?

Keep GitLab’s backend listener on plain HTTP when appropriate, preserve Host, X-Forwarded-Proto, X-Forwarded-Ssl, and X-Forwarded-Port, and run sudo gitlab-ctl reconfigure after changing package settings.

Why is the real client IP missing from GitLab logs?

Add the actual upstream proxy addresses to real_ip_trusted_addresses, select the header your proxy sends, and enable recursive processing when multiple trusted proxies are involved.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The Bottom Line

Pick one public listener owner, keep external_url identical to the URL users visit, and make every proxy hop preserve the host, HTTPS scheme, port, and client chain. Reconfigure GitLab after package-setting changes, reload custom NGINX safely, and configure Registry, Pages, and KAS separately. Most redirect and 422 failures are header or trust-boundary problems, not GitLab application defects.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.