Question

How to Use go get with Private Repositories in Go

gogit

Question

I want to use go get with a private repository hosted on GitLab, but I cannot get it to work correctly.

Here is my first attempt:

go get -v gitlab.com/secmask/awserver-go

This produced the following output:

Fetching https://gitlab.com/secmask/awserver-go?go-get=1
https fetch failed.
Fetching http://gitlab.com/secmask/awserver-go?go-get=1
Parsing meta tags from http://gitlab.com/secmask/awserver-go?go-get=1 (status code 200)
import "gitlab.com/secmask/awserver-go": parse http://gitlab.com/secmask/awserver-go?go-get=1: no go-import meta tags
package gitlab.com/secmask/awserver-go: unrecognized import path "gitlab.com/secmask/awserver-go"

It seems go get cannot access the repository metadata, likely because authentication is required.

My second attempt was to configure Git to replace HTTPS with SSH:

[url "ssh://git@gitlab.com/"]
    insteadOf = https://gitlab.com/

After that:

go get -v gitlab.com/secmask/awserver-go

still did not work, but this did:

go get -v gitlab.com/secmask/awserver-go.git

However, that causes the project directory to be created as:

src/gitlab.com/secmask/awserver-go.git

I can rename it manually, but I do not want to do that every time I run go get.

What is the proper way to use go get with a private repository, without ending up with the .git suffix in the package path?

Short Answer

By the end of this page, you will understand why go get behaves differently for private repositories, how Go resolves import paths, why .git sometimes appears to fix the problem, and the proper way to configure private module access using Git, SSH, and GOPRIVATE.

Concept

In Go, go get does not simply clone a repository the way git clone does. It first tries to resolve the import path and figure out where the source code lives.

For public repositories on well-known hosts like GitHub or GitLab, Go can usually detect the repository automatically. For private repositories, that process can fail because:

  • the host may require authentication before returning metadata
  • the repository path may not expose go-import meta tags
  • Go may try HTTPS first, even if your real access works through SSH

Historically, adding .git to the import path could help because it gave Go a strong hint that the path refers directly to a Git repository. But this changes the package path itself, which is usually not what you want.

For modern Go projects, the proper approach is usually:

  • keep the import path clean, without .git
  • configure Git authentication separately, often using SSH keys or access tokens
  • tell Go which module paths are private using GOPRIVATE

This matters because private dependencies are common in real applications:

  • internal company libraries
  • shared API clients
  • private SDKs
  • reusable utilities across services

If private repository access is configured correctly, Go tooling works smoothly for fetching, building, testing, and updating dependencies.

Mental Model

Think of go get like a delivery service trying to find a building.

  • The import path is the street address.
  • Go first checks whether that address clearly identifies the building.
  • If the address is vague, Go asks the hosting site for directions using metadata.
  • If the building is private, the guard at the gate may refuse to answer unless you are authenticated.

Adding .git is like writing extra directions on the address label: “this is definitely a Git repo.” That can help the delivery reach the right place, but now the label itself includes .git, which becomes part of the package path.

The better solution is to:

  • keep the address clean
  • make sure the delivery service has permission to enter
  • tell Go in advance that some addresses are private and should be handled differently
Take Quiz

Syntax and Examples

Basic private module setup

In modern Go, private repository access is typically configured outside the source code.

1. Mark the repository as private

go env -w GOPRIVATE=gitlab.com/secmask/*

This tells Go that modules matching this path are private.

2. Configure Git to use SSH instead of HTTPS

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

This makes Git rewrite GitLab HTTPS URLs to SSH URLs.

3. Make sure SSH access works

ssh -T git@gitlab.com

If SSH is configured correctly, GitLab should recognize your account.

4. Fetch the module

go get gitlab.com/secmask/awserver-go

Notice that the import path does not include .git.

Example import

package main

import (
    "fmt"
    "gitlab.com/secmask/awserver-go"
)

func main() {
    fmt.Println(awserver.Version)
}

The import path should match the module path exactly as intended by the repository, without manually appending .git unless the module itself is actually named that way.

If using modules

In a go.mod file, the dependency would appear like this:

module myapp

go 1.22

require gitlab.com/secmask/awserver-go v0.0.0-00010101000000-000000000000

In practice, Go fills in the version automatically when it can access the repository.

Step by Step Execution

Consider this command:

go get gitlab.com/example/internal-lib

Here is what happens conceptually:

Step 1: Go reads the module path

Go sees:

gitlab.com/example/internal-lib

It treats this as an import or module path, not just a Git URL.

Step 2: Go tries to resolve the repository

Go may try to access metadata at a URL such as:

https://gitlab.com/example/internal-lib?go-get=1

This is how it checks whether the path maps to a repository.

Step 3: Authentication may fail

If the repository is private, GitLab may not return the metadata Go expects unless you are authenticated or the host is recognized in a way Go can use.

Step 4: Git transport is used

Once Go knows where the repository is, Git is used underneath to fetch the source.

If your Git config says:

[url "ssh://git@gitlab.com/"]
    insteadOf = https://gitlab.com/

then Git can switch to SSH when cloning or fetching.

Step 5: Go stores the dependency

  • In older GOPATH workflows, it would appear under src/...
  • In module mode, it goes into the module cache

Why .git changes behavior

When you run:

go get gitlab.com/example/internal-lib.git

Go treats .git as part of the path. That can help repository detection, but it also means the directory and import path now include .git, which is usually undesirable.

So the fix is not to rely on .git, but to make private access work for the original module path.

Real World Use Cases

Private repository access in Go is commonly used in situations like these:

Internal shared libraries

A company may keep common code in a private module:

  • authentication helpers
  • logging wrappers
  • configuration loaders
  • database utilities

Example:

import "gitlab.com/acme/platform/auth"

Private SDKs

A backend team might publish an internal SDK for other teams to call company services.

Examples include:

  • billing clients
  • messaging service clients
  • analytics wrappers

Monorepo or multi-repo organizations

One service may depend on another internal package stored in a separate private repository.

CI/CD environments

Build servers often need to run:

go mod download
go test ./...

This only works when private repository authentication is configured correctly for non-interactive environments.

Take Quiz

Real Codebase Usage

In real projects, developers usually combine private module settings with a few standard patterns.

1. GOPRIVATE for internal modules

go env -w GOPRIVATE=gitlab.com/acme/*

This prevents Go from treating those modules like public ones through the public proxy and checksum database.

2. SSH keys or deploy keys

Teams often use:

  • personal SSH keys for local development
  • deploy keys or bot accounts in CI
  • access tokens when SSH is not available

3. Clean module paths

Developers keep imports stable and readable:

import "gitlab.com/acme/payments/client"

not:

import "gitlab.com/acme/payments/client.git"

4. Environment-based configuration

In CI pipelines, a setup step often runs before Go commands:

git config --global url."ssh://git@gitlab.com/".insteadOf https://gitlab.com/
go env -w GOPRIVATE=gitlab.com/acme/*

5. Validation and early failure

Teams usually verify access early:

ssh -T git@gitlab.com
go list -m gitlab.com/acme/internal-lib

This helps detect authentication issues before the main build starts.

Common Mistakes

1. Appending .git to the import path unnecessarily

This may appear to fix the problem:

go get gitlab.com/secmask/awserver-go.git

But it changes the module path itself.

Use this only if the repository is actually intended to be imported that way, which is uncommon.

2. Forgetting to configure authentication

Broken approach:

go get gitlab.com/secmask/awserver-go

If SSH keys or tokens are not set up, Go cannot fetch the private repository.

Fix:

  • configure SSH keys
  • or configure HTTPS credentials
  • test access with Git directly first

3. Not setting GOPRIVATE

Without GOPRIVATE, Go may try public module infrastructure for a private dependency.

Fix:

go env -w GOPRIVATE=gitlab.com/secmask/*

4. Confusing repository URLs with import paths

These are not always the same thing:

  • Git URL: ssh://git@gitlab.com/secmask/awserver-go.git
  • Go import path: gitlab.com/secmask/awserver-go

Go source code should usually use the import path, not the raw clone URL.

5. Assuming go get works like git clone

go get does more than cloning. It resolves module metadata and import paths first.

6. Testing only Go and not Git

If this fails:

git ls-remote git@gitlab.com:secmask/awserver-go.git

then Go will not be able to fetch the repository through Git either.

Test the lower-level access first.

Comparisons

ApproachWorks for private repos?Keeps clean import path?Notes
go get gitlab.com/user/repoYes, if properly configuredYesPreferred approach
go get gitlab.com/user/repo.gitSometimesNoAvoid unless the module path truly includes .git
git clone manuallyYesNot applicableUseful for testing access, but not how dependencies are normally managed
HTTPS with username/tokenYesYesCommon in CI or restricted environments
SSH with insteadOfYesYesCommon for developer machines

GOPATH mode vs module mode

ModeDependency locationRecommended today?
GOPATH modesrc/...No
Go modulesModule cache + go.modYes

SSH vs HTTPS for private Go modules

MethodProsCons
SSHConvenient for developers, key-based authRequires SSH key setup
HTTPS with tokenGood for automation and CIToken management required
Take Quiz

Cheat Sheet

# Mark GitLab paths as private
go env -w GOPRIVATE=gitlab.com/secmask/*

# Rewrite GitLab HTTPS URLs to SSH
git config --global url."ssh://git@gitlab.com/".insteadOf https://gitlab.com/

# Test SSH access
ssh -T git@gitlab.com

# Fetch private module
go get gitlab.com/secmask/awserver-go

Rules to remember

  • Use the normal import path, usually without .git
  • Configure authentication in Git or SSH, not in the import path
  • Set GOPRIVATE for private modules
  • Test Git access separately if go get fails
  • Prefer module mode over old GOPATH workflows

Quick diagnostics

# Check private module setting
go env GOPRIVATE

# Check Git rewrite rules
git config --global --get-regexp url

# Test repository access with Git
git ls-remote git@gitlab.com:secmask/awserver-go.git

Key idea

If adding .git makes it work, the real issue is usually repository detection or authentication, not that .git belongs in the import path.

FAQ

Why does go get try ?go-get=1?

Go uses that request to discover repository metadata for an import path. This helps it map the path to the correct version control source.

Should I include .git in a Go import path?

Usually no. Only do that if the module was intentionally defined that way, which is rare.

What does GOPRIVATE do?

It tells Go which module paths are private so it should not use the public proxy or checksum database for them.

Is SSH required for private repositories?

No. You can also use HTTPS with credentials or access tokens. SSH is just a common and convenient choice.

Why does Git work but go get still fail?

Because Go first resolves the import path before Git fetches the code. Authentication or metadata resolution may still be misconfigured.

How do I check whether my private module access is configured correctly?

Start with:

ssh -T git@gitlab.com
git ls-remote git@gitlab.com:secmask/awserver-go.git
go env GOPRIVATE

Does this differ between old Go and modern Go?

Yes. Older workflows used GOPATH and go get more directly for installation. Modern Go uses modules and go.mod, which is the recommended approach.

Take Quiz

Mini Project

Description

Create a small Go project that depends on a private module hosted on GitLab. The project will verify that your environment is correctly configured for private dependency access and will demonstrate the correct import path, Git setup, and module download flow.

Goal

Set up a Go project that successfully imports and builds against a private GitLab module without using .git in the import path.

Requirements

  1. Create a new Go module for a sample application.
  2. Configure private module access for a GitLab namespace using GOPRIVATE.
  3. Configure Git to use SSH for GitLab URLs.
  4. Import a package from a private repository using a clean Go import path.
  5. Build the program successfully.
Take Quiz

Keep learning

Related questions

View more