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.
- 01Choose one canonical domainGitLab redirects, cookies, assets and WebSocket endpoints must use the same public host.
- 02Prepare the GitLab originKeep the public GitLab URL on HTTPS; choose HTTP, verified HTTPS or an existing reverse proxy for the private connection.
- 03Create the BusinessProxy private appPublish GitLab through the connector and bind the custom domain that matches GitLab external_url.
- 04Add technical access for package toolsGive npm, Yarn and approved CI clients narrow machine-to-machine access without exposing the GitLab credential.
- 05Verify redirects, assets and WebSocketA successful setup returns the sign-in page and loads CSS, JavaScript and live endpoints from the same public domain.
- 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.
- 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.
- 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.
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.
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'] = falseexternal_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.
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.
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.
- 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.
- 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.
- 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.
# .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.
- 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.
- 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.
- 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.
- 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.
- 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.
<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.
- 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.
- 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.