Skip to main content

GitGuardian Bridge Self-Hosted

GitGuardian Bridge (ggbridge) lets your GitGuardian installation scan sources that it cannot reach directly, such as a GitLab instance in an isolated network or behind a firewall.

The bridge opens no inbound connection into your isolated network. The client inside that network dials out to the bridge server, and GitGuardian sends its scanning traffic back through that single connection.

info

Available from GitGuardian 2026.9.0.

GitGuardian Bridge must be enabled on your license. Contact your GitGuardian representative if the section described below does not appear in your dashboard.

This page covers GitGuardian Bridge on a self-hosted installation, where you install the bridge server yourself and declare bridges in your Helm values. On GitGuardian SaaS, bridges are created and managed from the dashboard instead: see GitGuardian Bridge, which also lists the services the bridge supports.

How it works​

A bridge is a pair of ggbridge releases:

  • a server, installed in the cluster that runs GitGuardian
  • a client, installed inside the isolated network

Each isolated network needs its own pair. If you have two isolated networks, install two servers and two clients.

Before you start​

  • A Kubernetes cluster running GitGuardian, where the bridge server goes.
  • A Kubernetes cluster inside the isolated network, where the client goes. If there is none, k3s/k3d on a single VM is enough — see the ggbridge repository for that setup.
  • A public DNS record pointing at the bridge server, reachable from the isolated network.
  • Outbound HTTPS allowed from the isolated network to that hostname.
  • Istio, Gateway API, or an Ingress controller in that cluster, to expose the server.

Throughout this page the example bridge is named network-a and reachable at network-a.bridge.example.com.

Step 1: Generate the certificates​

caution

You should rely on your own Certificate Authority and internal procedures to generate/get those certificates. The following is provided as a basic example.

The bridge authenticates both ends with mutual TLS, using certificates you provide. Create one Certificate Authority, one server certificate and one client certificate.

HOST=network-a.bridge.example.com

# Certificate Authority
openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \
-keyout ca.key -out ca.crt -subj "/CN=ggbridge-ca"

# Server certificate
printf 'subjectAltName=DNS:%s\nextendedKeyUsage=serverAuth\nbasicConstraints=critical,CA:FALSE\n' "$HOST" > server.ext
openssl req -newkey rsa:4096 -nodes -keyout server.key -out server.csr -subj "/CN=$HOST"
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out server.crt -days 825 -sha256 -extfile server.ext

# Client certificate
printf 'extendedKeyUsage=clientAuth\nbasicConstraints=critical,CA:FALSE\n' > client.ext
openssl req -newkey rsa:4096 -nodes -keyout client.key -out client.csr -subj "/CN=network-a-client"
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out client.crt -days 825 -sha256 -extfile client.ext
caution

The server certificate's subjectAltName must be exactly the hostname the client dials. The client verifies it and refuses to connect otherwise.

Step 2: Create the secrets​

In the GitGuardian cluster:

kubectl create namespace ggbridge
kubectl -n ggbridge create secret generic ggbridge-network-a-server-tls \
--from-file=ca.crt=ca.crt --from-file=tls.crt=server.crt --from-file=tls.key=server.key

In the isolated network:

kubectl create namespace ggbridge
kubectl -n ggbridge create secret generic ggbridge-network-a-client-tls \
--from-file=ca.crt=ca.crt --from-file=tls.crt=client.crt --from-file=tls.key=client.key

Step 3: Install the bridge server​

Choose how to expose the server​

The client connects from outside the cluster, so the server needs an entry point. Pick the one your cluster already runs — all three support mutual TLS and the path routing that the replicas rely on.

You already runAdd to your values
Istioserver.istio.enabled: true
Gateway APIserver.gateway.enabled: true
An Ingress controllerserver.ingress.enabled: true, plus server.ingress.controller for nginx or traefik

Istio

server:
istio:
enabled: true

Targets the gateway labelled istio: ingress. For a different label, set server.istio.gateway.selector. To attach to gateways you already manage, list them in server.istio.gateways and set server.istio.gateway.create: false.

Gateway API

server:
gateway:
enabled: true
gateway:
className: your-gateway-class

To attach to an existing Gateway instead, set server.gateway.parentRefs and server.gateway.gateway.create: false.

Ingress

server:
ingress:
enabled: true
controller: nginx # or traefik
className: nginx
caution

The chart wires client-certificate verification for nginx and traefik only. With another controller, add its own client-auth annotations through server.ingress.annotations, pointing at the CA in your TLS secret.

Do not expose the server with a plain LoadBalancer Service. A Service cannot route the /0, /1, /2 paths the replicas are reached on, so the chart refuses that too unless deploymentCount is 1.

Install​

Create server-values.yaml, combining the basics with the exposure block you chose. Replace domain with your own, and keep subdomain short and unique per bridge.

mode: server

subdomain: network-a
domain: bridge.example.com

tls:
enabled: true
existingSecret: ggbridge-network-a-server-tls

server:
istio:
enabled: true

Then install it:

helm -n ggbridge upgrade --install --create-namespace \
ggbridge-network-a oci://ghcr.io/gitguardian/ggbridge/helm/ggbridge \
-f server-values.yaml

Point your DNS record for network-a.bridge.example.com at the address your exposure method publishes.

caution

The server's proxy pods stay NotReady until the client from Step 5 connects. Their readiness probe tests the tunnel itself, and there is no tunnel yet, so kubectl get pods -n ggbridge showing 0/1 proxy pods at this point is expected. They become ready on their own once the client is running — no liveness probe restarts them in the meantime.

Because of this, the server release cannot reach a healthy state on its own. Do not gate it on readiness: helm --wait, and GitOps tools that wait for resource health such as Argo CD or Flux, will time out here. Install the server without waiting, then continue to Step 5.

Step 4: Declare the bridge in GitGuardian​

Add the GGBridge block to your GitGuardian Helm values, then upgrade your release.

GGBridge:
enabled: true
basePublicDomain: bridge.example.com
baseInternalDomain: ggbridge.svc.cluster.local
bridges:
- subdomain: network-a
name: Isolated network A
domains:
- gitlab.internal.example.com
  • basePublicDomain is the domain from Step 3.
  • baseInternalDomain is the cluster DNS suffix of the namespace holding the bridge servers, so <namespace>.svc.cluster.local. Install every bridge server in that one namespace.
  • subdomain must match the server release exactly.
  • domains are the hostnames GitGuardian should reach through this bridge.
caution

These values are the source of truth. Removing a bridge or a domain from them removes it from GitGuardian on the next upgrade, so keep the file under version control.

Step 5: Install the bridge client​

Create client-values.yaml inside the isolated network. hostname is the server's subdomain and domain joined together.

hostname: network-a.bridge.example.com

tls:
enabled: true
existingSecret: ggbridge-network-a-client-tls

Then install it:

helm -n ggbridge upgrade --install --create-namespace \
ggbridge-network-a oci://ghcr.io/gitguardian/ggbridge/helm/ggbridge \
-f client-values.yaml

Both sides run three replicas by default for high availability. If you change deploymentCount, set the same value on the server and the client.

Step 6: Verify​

In GitGuardian, open Settings → Workspace → Security. The bridge appears in the GitGuardian bridge (ggbridge) section and turns Connected within about a minute of the client starting.

Then add your source, for example your self-hosted GitLab, and run a scan. Traffic to the domains you listed now travels through the bridge.

note

On a self-hosted installation, bridges are managed entirely through your Helm values, so this section is read-only.

Add another isolated network​

Repeat the whole process for the second network, with its own subdomain and hostname:

  1. Issue a new server and client certificate from the same CA, with the new hostname in the server certificate.
  2. Install a second bridge server in the same namespace as the first.
  3. Add a second entry to GGBridge.bridges.
  4. Install a client in the new network.

Each network must have its own pair. A single bridge server cannot serve two networks.

Troubleshooting​

SymptomWhat to check
The section is missing from the dashboardGitGuardian Bridge is not enabled on your license.
The bridge is licensed but the section stays emptyBridges are created during a deployment. If GitGuardian Bridge was enabled on your license after your last upgrade, upgrade again.
The server's proxy pods are NotReady after Step 3Expected until the client connects: the readiness probe tests the tunnel. Continue to Step 5.
Installing the server times out or never goes healthyYou gated the release on readiness (helm --wait, or a GitOps health check). The server cannot be healthy before its client exists — install it without waiting.
The bridge stays Waiting for first connectionThe client cannot reach the server. Check DNS, outbound firewall rules, and the client logs.
The client logs a certificate errorThe server certificate's subjectAltName does not match the hostname the client dials, or the two sides do not share the same CA.
The bridge is connected but scans failThe domain is missing from GGBridge.bridges[].domains, or the source is not reachable from the client's network.

For more detail, see the ggbridge troubleshooting guide.