Fixing 'exit status 128' When Fetching Private Go Modules

intermediate🔷 Go2026-07-08| Go 1.13+, Git, Linux/macOS/Windows, GitHub/GitLab/Bitbucket private repositories

Error Message

go get github.com/acme-corp/secret-api: git ls-remote -q https://github.com/acme-corp/secret-api.git: exit status 128
#go#git#devops#golang#github

The 2 AM Pipeline Failure

It’s 2 AM. You’re pushing a critical hotfix to production. The CI/CD pipeline starts, but instead of a green checkmark, you get a wall of red text. Or maybe you're just setting up a new hire's laptop. You run go mod tidy, and everything stalls. The terminal throws this at you:

go get github.com/acme-corp/secret-api: git ls-remote -q https://github.com/acme-corp/secret-api.git: exit status 128

This exit status 128 is Git’s generic way of saying "Access Denied." It happens because the Go toolchain is trying to reach a private repository as if it were a public one. The authentication handshake fails, and Git shuts down the connection.

Why Your Build is Breaking

By default, Go assumes every module is public. This creates two major roadblocks for private code:

  • The Google Proxy: Go usually fetches modules from proxy.golang.org. Since your repo is private, Google’s proxy can't see it. It returns a 404 or 410 error, and your build dies.
  • The HTTPS Trap: Go tries to fetch modules via HTTPS. If your machine uses SSH keys for GitHub but doesn't have a credential helper for HTTPS, Git will fail. It can't ask for your password in the middle of a non-interactive Go command.

Step 1: Define Your Private Space

Start by telling Go which modules should skip the public proxy and checksum database. Use the GOPRIVATE environment variable for this. It accepts comma-separated patterns.

Run this command in your terminal. Replace github.com/acme-corp with your actual organization:

go env -w GOPRIVATE=github.com/acme-corp/*

This simple flag does a lot. It tells Go that anything under the acme-corp path is private. Go will now bypass the Google proxy and the public checksum database (GOSUMDB) entirely for these repos.

Step 2: Force Git to Use SSH

Setting GOPRIVATE gets Go to talk to GitHub directly, but it still defaults to HTTPS. If you rely on SSH keys for development, you need to force Git to swap protocols on the fly.

Add this rewrite rule to your global .gitconfig:

git config --global url."git@github.com:".insteadOf "https://github.com/"

Now, when Go runs git ls-remote https://github.com/acme-corp/secret-api.git, Git intercepts it. It swaps the URL to git@github.com:acme-corp/secret-api.git. Your SSH agent handles the auth, and the download succeeds instantly.

CI/CD Alternative: Personal Access Tokens

In environments like GitHub Actions or Jenkins, managing SSH keys is often a hassle. Using a Personal Access Token (PAT) via HTTPS is usually cleaner.

Instead of the SSH rewrite, use this command in your CI script:

# For GitHub Actions
git config --global url."https://${GH_TOKEN}:x-oauth-basic@github.com/".insteadOf "https://github.com/"

Security Tip: Never hardcode this token. Use environment variables or secrets management to inject the ${GH_TOKEN} at runtime to keep your credentials safe.

Step 3: Verify the Fix

Clear your local module cache to ensure you aren't looking at old, broken metadata. Then, try to resolve the dependencies again.

go clean -modcache
go mod tidy

If the command finishes silently, you've won. You can double-check your settings anytime by running go env GOPRIVATE.

Common Pitfalls to Watch For

1. The Colon vs. Slash Trap

When using insteadOf, the syntax is picky. If you use git@github.com: (with a colon), ensure the HTTPS side matches with https://github.com/ (with a trailing slash). A tiny mismatch here results in broken URLs like git@github.com:/user/repo, which will trigger another 128 error.

2. The Checksum Database

Some developers try to use GONOPROXY alone. Don't do this. If you skip the proxy but not the checksum database (GOSUMDB), Go will still try to verify your private code against a public ledger. It will fail because your private code isn't in that ledger. GOPRIVATE is the safer bet because it configures both settings at once.

3. Docker Container Issues

Docker builds are a frequent source of 128 errors. The container is a clean slate; it doesn't know about your host's .gitconfig or SSH keys. You must explicitly set the environment variables inside your Dockerfile or pass them as build arguments.

# Example Dockerfile snippet
ARG GITHUB_TOKEN
ENV GOPRIVATE=github.com/acme-corp/*
RUN git config --global url."https://${GITHUB_TOKEN}:x-oauth-basic@github.com/".insteadOf "https://github.com/"

Summary

  • Go's default setup assumes all code is public. For internal projects, GOPRIVATE is your best friend.
  • An exit status 128 almost always boils down to a Git authentication failure.
  • The insteadOf trick is the cleanest way to fix protocol mismatches without touching your go.mod file.

Related Error Notes