Maintain a Go Module Safely with go mod

go mod is the command you reach for when a build says a module is missing or go.sum looks wrong. This guide initialises or inspects a module, previews dependency cleanup before it writes anything, verifies the module cache, and vendors a copy only when a build actually needs one.

Allow about fifteen minutes for an existing project, longer if dependencies must be downloaded. The examples use the installed go command, which reports Go 1.26.1 on Linux amd64. The Debian package database reports golang-go version 2:1.22~2build1, so check both the binary and package version on your own host before relying on version-specific output.

Most commands below are ordinary, unprivileged operations. They read or change files in the current module and may use the network through the configured module proxy. Do not run them with sudo: root-owned go.mod, go.sum or module-cache files create a separate permissions problem you will meet later, at a worse time.

1. Check the tool and module root

Start from the directory that contains the project, then confirm which executable and package you are using:

$ command -v go
/usr/local/go/bin/go
$ go version
go version go1.26.1 linux/amd64
$ dpkg-query -W -f='${Package} ${Version}\n' golang-go
golang-go 2:1.22~2build1

Go module commands normally find go.mod in the current directory or one of its parents. Check the path before changing anything:

$ go env GOMOD
/home/you/src/example/go.mod

If this prints /dev/null, you are not inside a normal module. Move to the intended project directory, or initialise one in the current directory. A module path becomes part of package import paths, so choose the repository path rather than a casual local nickname when other code will import it.

2. Initialise a new module only when needed

For a new project with Go source files but no go.mod, run:

$ go mod init example.com/acme/report
go: creating new go.mod: module example.com/acme/report
go: to add module requirements and sums:
	go mod tidy

The optional argument is the module path. Without it, go mod init tries to infer one from import comments or the current directory in GOPATH mode. The command refuses to run when go.mod already exists, and for good reason: choosing the wrong path can force edits to imports and downstream build files later.

Checkpoint: confirm the result without modifying it:

$ sed -n '1,20p' go.mod
module example.com/acme/report

go 1.26.1

The exact go directive can differ. Keep the file under version control before continuing.

3. Inspect go.mod before editing it

go mod edit is a low-level file editor for tools and scripts. It does not resolve modules or calculate a dependency graph. Use its read-only output modes to understand the current file first:

$ go mod edit -print
module example.com/acme/report

go 1.26.1

require example.com/acme/parser v1.4.0
$ go mod edit -json
{"Module":{"Path":"example.com/acme/report"},"Go":"1.26.1",...}

The JSON is useful to scripts, but its full contents depend on the file. For routine dependency upgrades or downgrades, prefer go get: the installed help explicitly directs users there because it keeps related module requirements consistent. Reserve go mod edit for controlled metadata changes, such as formatting:

$ cp --preserve=mode,timestamps go.mod go.mod.before-edit
$ go mod edit -fmt
$ git diff -- go.mod

Editing flags write the file. If the diff is not wanted, restore the backup with mv go.mod.before-edit go.mod, or use your version-control recovery workflow. Do not apply a guessed -replace directive to hide a failed download: local replacements change what is built and should be documented, not buried.

4. Preview and apply dependency cleanup

Once source imports are settled, use go mod tidy -diff to ask what cleanup would change, without writing go.mod or go.sum:

$ go mod tidy -diff
diff go.mod.orig go.mod
--- go.mod.orig
+++ go.mod
@@
- example.com/acme/unused v1.0.0
+ example.com/acme/parser v1.4.0
$ printf 'status: %s\n' "$?"
status: 1

A non-zero status from -diff means a diff was found, not necessarily that the module is broken. Review the proposed changes, then run the writing form:

$ go mod tidy
$ git diff -- go.mod go.sum

Tidy adds modules needed by the packages and tests in the module, removes unused requirements, and updates sums. It can load packages and contact the module proxy, so run it after changing imports, not as an unexplained repair step in a production checkout. If a dependency cannot be loaded, go mod tidy -e attempts to continue but can leave an incomplete result: treat its output as a diagnostic and inspect the diff carefully.

5. Download and verify dependencies

Go downloads modules automatically when ordinary commands need them. Use go mod download when you deliberately want to pre-fill the local cache or pull machine-readable module metadata:

$ go mod download
$ go mod download -json example.com/acme/[email protected]
{
  "Path": "example.com/acme/parser",
  "Version": "v1.4.0",
  "Dir": "/home/you/go/pkg/mod/example.com/acme/[email protected]",
  "Sum": "h1:..."
}

Paths and checksums vary, and a failed download produces a JSON object with an error instead. Do not paste a real private module path or checksum into a public bug report without checking whether it reveals repository information.

Once the cache is populated, run:

$ go mod verify
all modules verified.

Tip: this checks downloaded source against the recorded content hashes. It does not prove a dependency is safe, current or appropriate for the application, and it does not scan source for vulnerabilities. A modified module produces a non-zero result; remove and redownload it through your normal Go cache process rather than editing go.sum by hand.

6. Understand the graph and a dependency's purpose

Use go mod graph for the requirement graph. Each normal line is a module followed by one of its requirements:

$ go mod graph
example.com/acme/report example.com/acme/[email protected]
example.com/acme/[email protected] example.com/acme/[email protected]

The graph describes requirements, not every package import. To ask why a package or module is reachable at all, use go mod why:

$ go mod why -m example.com/acme/parser
# example.com/acme/parser
example.com/acme/report/internal/load
example.com/acme/parser

An unneeded target is reported with a parenthesised note. The default query includes tests for reachable packages; add -vendor when you specifically want to exclude dependency tests from the explanation. These commands require a module root, so return to the directory containing go.mod if they report that none is present.

7. Vendor only when the build requires it

go mod vendor resets the module's vendor directory to contain the packages needed to build and test the module. It changes many files at once and can make a large review noisy:

$ git status --short
$ go mod vendor
$ git status --short
 M go.mod
 M go.sum
?? vendor/

There is no elevated-privilege requirement here. Make a commit or a recoverable backup before running it, and review the generated tree before selecting vendor mode for a build. The -o option can write to a different directory, but Go only treats a directory literally named vendor within the module root as its automatic vendor directory, so a staging directory is useful for inspection, not as a silent replacement for the project's vendor policy.

Warning: do not run vendor generation during an incident or release without checking the diff. If it is wrong, stop using the generated directory and restore the previous tracked vendor, go.mod and go.sum with your repository's reviewed restore command. Keep the original dependency files until the build and tests pass.

Common traps

Done means