The missing piece
You clone a repository, run go run main.go, and the terminal immediately throws module not found. The build stops. Your editor highlights an import in red. You stare at a perfectly valid import path and wonder why the toolchain refuses to cooperate. The error rarely means the package doesn't exist. It means Go's dependency resolver hit a dead end while trying to locate the module definition, verify its checksum, or fetch it from a network source.
How Go finds your dependencies
Go modules work like a coordinated library system. Your go.mod file is the catalog. It lists every external package your code touches and pins the exact version you tested against. Your go.sum file is the security seal. It stores cryptographic hashes for every downloaded module so the toolchain can verify nothing got tampered with during transit. When you ask Go to build or run your code, it checks the catalog, looks for the files in your local cache, and if they are missing, contacts a module proxy to fetch them. If the proxy is unreachable, the catalog entry is malformed, or your network blocks the request, you get the module not found error.
The module proxy is not an optional extra. It is the default distribution channel for public Go packages. The official proxy at proxy.golang.org caches every public module, verifies its contents against the checksum database, and serves it with a consistent URL structure. This design removes the need to clone git repositories directly during builds. It makes builds reproducible and fast.
Treat go.mod as a contract. Treat go.sum as a receipt. Keep both in version control.
A minimal reproduction
Create a fresh directory and write a single file that imports a third-party package.
package main
import (
"fmt"
"github.com/gorilla/mux" // External router package
)
// main prints a simple routing setup to verify the import works.
func main() {
r := mux.NewRouter()
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello")
}).Methods("GET")
fmt.Println("Router initialized")
}
Run go run main.go without initializing a module. The compiler rejects the program with no required module provides package github.com/gorilla/mux: go.mod file not found in current directory or any parent directory; see 'go help modules'. The toolchain refuses to guess. It needs a go.mod file to know which version of mux to fetch.
Fix it by initializing the module and syncing the dependency list.
go mod init example.com/myapp
go mod tidy
The go mod tidy command scans your source files, adds missing dependencies to go.mod, removes unused ones, and populates go.sum. Run go run main.go again and the program compiles. The resolver found the module, downloaded it, verified the checksum, and placed it in your module cache.
Always run go mod tidy before committing. It keeps your dependency graph honest.
What happens behind the scenes
When you invoke go build, go run, or go test, the toolchain follows a strict resolution order. It starts by checking your local module cache at $GOPATH/pkg/mod. If the exact version is already cached and the checksum matches go.sum, it uses it immediately. No network call happens. This is why builds feel instant after the first run.
If the version is missing, Go contacts the module proxy. The proxy responds with a go.mod file for that version, which lists the module's own dependencies. Go then downloads the .zip archive containing the source code, verifies it against the checksum database, and stores it in your cache. The entire process is deterministic. The same go.mod and go.sum will always produce the same set of files on any machine.
Private repositories break this flow. The public proxy cannot access GitHub Enterprise, GitLab, or internal artifact registries. When you import a private package, the proxy returns a 404 or a 401. The toolchain falls back to direct VCS access, which requires proper authentication and often triggers module not found if your credentials are misconfigured or if the proxy fallback is disabled.
The module system trusts the proxy by default. Trust it back by keeping your go.mod clean and your environment variables explicit.
Real world dependency management
Production code rarely imports a single package. You will pull in HTTP clients, JSON parsers, logging frameworks, and database drivers. Each brings its own transitive dependencies. Managing them manually is impossible. You rely on the toolchain to resolve the graph, but you still need to configure network access correctly.
Check your proxy settings with go env. Look for GOPROXY. The default value is https://proxy.golang.org,direct. The direct suffix tells Go to fall back to the original VCS host if the proxy fails. This fallback is helpful for private modules, but it can mask network issues if you are behind a corporate firewall that blocks git hosts.
If your company hosts an internal proxy, you override the default.
go env -w GOPROXY=https://internal-proxy.corp,direct
Private repositories require a separate configuration. The GOPRIVATE environment variable tells the toolchain which module paths should skip the public proxy and go straight to VCS.
go env -w GOPRIVATE=*.corp,github.com/mycompany/*
When GOPRIVATE matches an import path, Go bypasses proxy.golang.org entirely. It uses your configured git credentials, SSH keys, or HTTP tokens to fetch the source. If authentication fails, you get module not found or go: errors parsing go.mod: module path contains a non-ASCII character. The error message points to the network layer, not your code.
The convention is clear. context.Context always goes first. GOPROXY and GOPRIVATE always go in your shell profile or CI environment. Never hardcode proxy URLs in go.mod.
Keep network configuration out of source control. Keep dependency versions in.
Common traps and how to escape them
The module not found error usually stems from one of four scenarios. Misconfigured proxy settings block public packages. Missing or stale go.mod entries leave the resolver guessing. Private repository authentication fails silently until the build step. Checksum mismatches occur when go.sum gets out of sync with the actual downloaded files.
If you see go: module github.com/example/pkg: not found: module lookup disabled by GOPROXY=off, your environment explicitly disabled the proxy. Restore it with go env -w GOPROXY=https://proxy.golang.org,direct.
If you encounter verifying github.com/example/pkg@v1.2.3: checksum mismatch, your go.sum contains an old hash. Delete the stale entry and run go mod tidy to regenerate it. The toolchain will fetch the correct version and update the file.
If you get go: github.com/mycompany/internal@v0.0.0-20231001120000-abc123: invalid version: module contains a go.mod file with the module path "github.com/mycompany/internal/v2", you are importing a major version incorrectly. Go requires major versions v2 and above to include a /v2 suffix in the import path. Update your import to github.com/mycompany/internal/v2 and run go get to fetch the correct version.
The compiler will also reject builds with go: updates to go.mod needed; modify as follows: go get github.com/example/pkg@v1.5.0. This happens when a dependency requires a newer Go version or a different module version than what is pinned. Run the suggested command or update go.mod manually.
Never ignore checksum errors. They protect you from supply chain attacks. Always verify your proxy configuration before blaming the module system.
Picking the right tool for the job
Use go mod tidy when you want to synchronize your dependency graph with your actual imports. Use go get when you need to fetch a specific version of a package or upgrade a transitive dependency. Use GOPROXY when you need to route all public module requests through a corporate mirror or internal cache. Use GOPRIVATE when you work with internal repositories that the public proxy cannot access. Use go mod vendor when you need to ship a completely self-contained archive for environments without network access. Use plain go.mod editing when you must pin a dependency to a commit hash or override a transitive version.
The module system handles the heavy lifting. You handle the configuration.