Stable

Run GitLab behind BusinessProxy App Gateway

Step-by-step setup for publishing a self-hosted GitLab through BusinessProxy without exposing the GitLab server directly.

Steps

Follow these in order.

  1. 01Choose one canonical domainGitLab redirects, cookies, assets and WebSocket endpoints must use the same public host.
  2. 02Prepare the GitLab originKeep the public GitLab URL on HTTPS; choose HTTP, verified HTTPS or an existing reverse proxy for the private connection.
  3. 03Create the BusinessProxy private appPublish GitLab through the connector and bind the custom domain that matches GitLab external_url.
  4. 04Add technical access for package toolsGive npm, Yarn and approved CI clients narrow machine-to-machine access without exposing the GitLab credential.
  5. 05Verify redirects, assets and WebSocketA successful setup returns the sign-in page and loads CSS, JavaScript and live endpoints from the same public domain.
  6. 06Troubleshoot common failures502, pending assets, redirect loops and CSRF failures usually point to URL, scheme or upstream TLS mismatch.

Reference

Implementation details for setup and review.

When to use this guideUse this setup when GitLab is self-hosted in a private network and should be opened only through a managed BusinessProxy private-app session. BusinessProxy controls who can open the GitLab web app; GitLab still controls GitLab users, projects, groups, permissions and repository access inside GitLab.
  • Do not leave the GitLab upstream directly reachable from the public internet.
  • Prefer a permanent customer domain such as gitlab.example.com. A system address is also valid if you use its exact unique hostname shown by BusinessProxy as the canonical URL.
  • Run the BusinessProxy connector where it can reach the selected internal GitLab HTTP or HTTPS endpoint.
1. Pick the canonical public domainGitLab is sensitive to its public URL. Redirects, CSRF checks, cookies, assets and WebSocket URLs are generated from GitLab external_url. Choose one domain users will type in the browser, then use that exact domain everywhere.
  • Recommended shape: https://gitlab.example.com.
  • Avoid opening the same GitLab through several public aliases; cookies and redirects can become inconsistent.
  • Create the DNS record required by the BusinessProxy custom-domain flow and wait until the certificate is active.
2. Configure GitLab for reverse-proxy modeThe public URL and the private transport are separate settings. external_url describes the HTTPS address in the user browser; listen_https controls only the embedded NGINX listener. For GitLab 19.1 and earlier, including 18.11, the HTTP-upstream configuration below needs no local TLS certificate.
external_url 'https://gitlab.example.com'
nginx['listen_port'] = 80
nginx['listen_https'] = false
nginx['redirect_http_to_https'] = false
letsencrypt['enable'] = false
  • Run sudo gitlab-ctl reconfigure after editing /etc/gitlab/gitlab.rb.
  • Set Internal upstream to the private address, for example http://gitlab.internal:9080 when Docker maps private port 9080 to container port 80. Never put that internal IP, hostname or port into external_url.
  • Keep one active external_url declaration. Ruby evaluates the file in order, so a later HTTP declaration overrides an earlier HTTPS declaration. Review explicit proxy_set_headers overrides as well.
  • Use HTTP only on the trusted private hop. Restrict access to the origin port to connector hosts; the public browser connection and the BusinessProxy connector tunnel remain encrypted.
2a. Configuration keys in GitLab 19.2 and laterGitLab 19.2 moved Rails-specific NGINX settings under gitlab_rails[nginx]. Use the keys appropriate to your installed version; do not copy a configuration for another major version without checking it.
external_url 'https://gitlab.example.com'
gitlab_rails['nginx']['listen_port'] = 80
gitlab_rails['nginx']['listen_https'] = false
gitlab_rails['nginx']['redirect_http_to_https'] = false
letsencrypt['enable'] = false
2b. HTTPS between the connector and GitLabUse this variant when the private network also requires encryption. Keep the same canonical external_url and enable TLS on the internal listener. The example uses GitLab 19.1-or-earlier keys; on 19.2+ use the gitlab_rails[nginx] keys above.
external_url 'https://gitlab.example.com'
nginx['listen_port'] = 443
nginx['listen_https'] = true
nginx['ssl_certificate'] = '/etc/gitlab/ssl/gitlab.example.com.crt'
nginx['ssl_certificate_key'] = '/etc/gitlab/ssl/gitlab.example.com.key'
  • Use an HTTPS upstream whose hostname matches the certificate, for example https://gitlab.example.com:9443 with private DNS resolving that name directly to GitLab and Docker mapping private port 9443 to container port 443. Do not resolve it back to the BusinessProxy gateway.
  • Keep Verify upstream TLS enabled. Install the issuing CA in the connector runtime trust store when using a private CA. A certificate for a DNS name does not validate a bare-IP upstream unless the certificate also contains that IP.
2c. GitLab behind an additional internal reverse proxyIf the connector reaches an existing NGINX or ingress rather than GitLab directly, that proxy must preserve the public HTTPS identity even when its next hop is HTTP. For a dedicated virtual host used only by this protected HTTPS route, the following NGINX location is an example.
location / {
    proxy_pass http://gitlab.internal:80;
    proxy_set_header Host gitlab.example.com;
    proxy_set_header X-Forwarded-Host gitlab.example.com;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header X-Forwarded-Ssl on;
    proxy_set_header X-Forwarded-Port 443;
}
  • Do not replace the original HTTPS scheme with the internal HTTP $scheme. Accept forwarded identity only from trusted connector/proxy addresses, not arbitrary internet clients.
  • For a non-Omnibus or Helm installation, configure its documented public URL and trusted-proxy settings instead of applying gitlab.rb examples to an unsupported deployment.
3. Check GitLab from the connector hostBefore publishing the app, confirm that the connector host can reach GitLab by the same internal URL you will configure as upstream. This isolates GitLab and network problems from BusinessProxy routing problems.
curl -I http://gitlab.internal/users/sign_in
curl -I -H "Host: gitlab.example.com" http://gitlab.internal/users/sign_in
  • The response should be HTTP 200 or a normal GitLab redirect, not a TLS error or connection timeout.
  • If GitLab redirects to another host, fix external_url before continuing.
4. Create the private app in BusinessProxyIn the workspace dashboard, create a private app for GitLab. Select the connector that can reach GitLab and set the internal HTTP or HTTPS upstream for your chosen variant. Use either the exact generated system address or an active custom domain as the canonical external_url.
  • Public/custom domain: gitlab.example.com.
  • Upstream URL: http://gitlab.internal or another internal HTTP URL reachable by the connector.
  • Rewrite redirects and cookie domains should stay enabled unless support asks otherwise.
  • Verify upstream TLS can stay disabled only when the upstream URL is HTTP. For HTTPS upstreams, the setting controls certificate verification, not GitLab URL generation.
5. Use technical access for package managers and CITechnical access is for package managers, build agents and API clients that cannot create an interactive BusinessProxy browser session. It uses two independent tokens: the client presents a short-lived BusinessProxy token, while BusinessProxy keeps the GitLab credential encrypted and injects it only into an allowed upstream request.
  • npm or Yarn sends the BusinessProxy client token as Authorization: Bearer bp_m2m_*. BusinessProxy validates the token lifetime, private app, HTTP method and path prefix.
  • Before forwarding, BusinessProxy removes the client Authorization header, cookies, GitLab token headers and untrusted identity or forwarding headers. The bp_m2m token is never sent to GitLab.
  • BusinessProxy injects the configured upstream credential. GitLab then independently checks that deploy or access token and its project, group and Package Registry permissions.
  • The BusinessProxy client token and the GitLab token are different secrets. Do not put a GitLab token into BUSINESSPROXY_CLIENT_TOKEN or distribute it to package-manager clients.
6. Create the two-token credentialOpen the GitLab private app settings and use Technical access. Create a dedicated GitLab Project or Group Deploy Token with the smallest required scope, normally read_package_registry for downloads. Do not reuse a personal, owner or administrator token.
  • Set a recognizable credential name and a lifetime from 1 to 90 days. The default is 30 days.
  • Add only the required GitLab API path prefixes. A prefix matches the exact path and its child segments, not a similarly named path.
  • Allow GET and HEAD for package downloads. Add POST, PUT, PATCH or DELETE only for a separately reviewed publish or API workflow.
  • Choose the upstream authentication type supported by GitLab and enter the GitLab token as its secret. For the npm Package Registry, use the GitLab-supported Bearer token flow unless your installation requires an explicitly tested Basic or custom-header mode.
  • After creation, store the generated BusinessProxy client token immediately. It is shown only once; rotation returns a new token and immediately invalidates the previous one.
7. Configure npm or Yarn for a controlled canarynpm and Yarn must send the BusinessProxy client token as an Authorization Bearer value. Keep the token in a protected environment variable instead of committing it to the repository. The examples below show the accepted authentication contract; replace the host, scope and project ID with your values.
# .npmrc (npm 10 or Yarn 1.22)
@your-group:registry=https://gitlab.example.com/api/v4/projects/PROJECT_ID/packages/npm/
//gitlab.example.com/api/v4/projects/PROJECT_ID/packages/npm/:_authToken=${BUSINESSPROXY_CLIENT_TOKEN}
always-auth=true

# .yarnrc.yml (Yarn 4)
npmScopes:
  your-group:
    npmRegistryServer: "https://gitlab.example.com/api/v4/projects/PROJECT_ID/packages/npm/"
    npmAlwaysAuth: true
    npmAuthToken: "${BUSINESSPROXY_CLIENT_TOKEN}"
  • Run an end-to-end install with the exact npm or Yarn version before relying on this route. npm 10, Yarn 1.22 and Yarn 4 compatibility is not yet certified as production-proven.
  • The relay currently rejects paths containing an encoded slash (%2F). Scoped-package clients can generate such metadata URLs, so a scoped npm or Yarn request may fail before reaching GitLab.
  • Do not broaden the path allowlist to /api/v4 or enable write methods to work around a failed package request. Capture the failing path and verify the client contract first.
8. Keep technical access boundedTechnical credentials are managed only by workspace owners and administrators and require an active App Gateway route and a ready connector. They do not turn browser sessions into API credentials and do not bypass GitLab authorization.
  • A fresh account MFA assertion is required to create a credential, change paths or methods, change upstream authentication, or rotate the client token.
  • Revocation immediately invalidates the client token. It intentionally remains available without a new MFA assertion so access can be stopped during an incident.
  • Upstream secrets are encrypted at rest. Raw BusinessProxy client tokens are shown once and stored by BusinessProxy only as a keyed hash.
  • Technical access is restricted to the private app primary origin and the configured path/method allowlist. Related origins, browser cookies, BusinessProxy identity headers, WebSocket and HTTP Upgrade cannot be reused for machine-to-machine access.
9. Open GitLab and verify the browser sessionLaunch GitLab from BusinessProxy or open the custom domain after a valid app session is created. The sign-in page should load as HTML 200, and CSS, JavaScript, images and WebSocket requests should use the same public GitLab domain.
  • Extension only requires the extension access path and a valid protected application session. BusinessProxy portal with 2FA allows supported browser access after the required verification. A branded portal uses the configured customer-domain sign-in flow; it does not make GitLab public.
  • The system address and every custom address have separate launch modes. Check the mode for the exact hostname you are opening. An already established session in another tab is not proof of anonymous access.
  • Expected URL in the browser: https://gitlab.example.com/users/sign_in.
  • DevTools Network should not show static files hanging forever as pending.
  • If GitLab opens but later redirects to a different host, fix GitLab external_url and reconfigure GitLab again.
Common symptom: 502 from BusinessProxyA 502 means BusinessProxy could not get a usable response from the GitLab upstream through the connector. If the upstream is HTTPS, the cause is often certificate verification. If the upstream is HTTP, check connector readiness, DNS, TCP reachability and the configured upstream URL.
  • Use the private app diagnostics first; it checks the path from connector to upstream.
  • Disabling Verify upstream TLS helps only with upstream certificate trust. It does not fix a wrong external_url or redirect loop.
Common symptom: page is 200 but assets stay pendingWhen /users/sign_in returns 200 but CSS or JavaScript never finishes loading, GitLab is usually generating asset URLs, redirects or headers for a different public URL or scheme. Re-check external_url, listen_https, the public custom domain and Host handling.
  • external_url must equal the canonical public HTTPS address, whether a custom domain or the exact unique system address shown by BusinessProxy. It must not be the private upstream URL.
  • If GitLab is behind BusinessProxy TLS termination, nginx listen_https should normally be false.
  • Clear browser cache or service worker state after changing GitLab public URL settings.
Common symptom: Mixed Content or HTTP GraphQL requestsIf an HTTPS page tries to call http://gitlab.example.com/api/graphql, the browser blocks that request before it reaches BusinessProxy. The private HTTP upstream does not require HTTP links in the browser. Check the effective GitLab public URL, later configuration overrides and every intermediate proxy.
  • external_url must use HTTPS while listen_https may remain false. GitLab must see X-Forwarded-Proto: https for public browser traffic; an explicit HTTP proxy header can undo correct outer TLS termination.
  • After reconfigure, reload the page and verify GraphQL uses HTTPS and WebSocket uses WSS. Do not disable browser mixed-content protection or make the application public to hide the error.
Common symptom: manifest.json returns 401Check the main HTML document, GraphQL and manifest separately. A manifest failure alone does not prove the GitLab upstream is down. A protected manifest needs application-session credentials even when it is served from the same origin.
<link rel="manifest" href="/-/manifest.json" crossorigin="use-credentials">
  • A request without a valid BusinessProxy app session can return 401 by design. Do not add an anonymous exception for the manifest. If an authenticated page still gets 401 only for the manifest, inspect whether the link requests it with credentials.
  • HTTP forwarding covers the GitLab web UI, HTTPS repository operations and permitted API routes, not raw SSH. Configure the GitLab SSH endpoint and firewall separately; technical API credentials do not authorize SSH.
Common symptom: sign-in or CSRF failsGitLab sign-in and CSRF protection depend on scheme, host and cookies. If the form loads but sign-in fails unexpectedly, check that the browser stays on the canonical domain and that cookies are set for that domain only.
  • Do not mix gitlab.example.com and a *.business-proxy.com system alias in the same browser session.
  • After changing external_url, sign out and clear old GitLab cookies for the previous host.
Operational notesBusinessProxy protects the web access path to GitLab, but it does not replace GitLab administration. Keep GitLab user lifecycle, project permissions, SSH clone policy, runners and backups under your normal GitLab controls.
  • For the first rollout, test web sign-in, project browsing, repository view, merge requests, artifacts and WebSocket/live UI behavior.
  • If users also need Git over SSH, plan that separately. This guide covers the GitLab web application path.
  • Keep a direct administrative recovery path available on a restricted private network for maintenance.