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.
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/k3don 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
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
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 run | Add to your values |
|---|---|
| Istio | server.istio.enabled: true |
| Gateway API | server.gateway.enabled: true |
| An Ingress controller | server.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
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.
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
basePublicDomainis thedomainfrom Step 3.baseInternalDomainis the cluster DNS suffix of the namespace holding the bridge servers, so<namespace>.svc.cluster.local. Install every bridge server in that one namespace.subdomainmust match the server release exactly.domainsare the hostnames GitGuardian should reach through this bridge.
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.
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:
- Issue a new server and client certificate from the same CA, with the new hostname in the server certificate.
- Install a second bridge server in the same namespace as the first.
- Add a second entry to
GGBridge.bridges. - Install a client in the new network.
Each network must have its own pair. A single bridge server cannot serve two networks.
Troubleshooting
| Symptom | What to check |
|---|---|
| The section is missing from the dashboard | GitGuardian Bridge is not enabled on your license. |
| The bridge is licensed but the section stays empty | Bridges 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 3 | Expected until the client connects: the readiness probe tests the tunnel. Continue to Step 5. |
| Installing the server times out or never goes healthy | You 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 connection | The client cannot reach the server. Check DNS, outbound firewall rules, and the client logs. |
| The client logs a certificate error | The 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 fail | The 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.