Managing Multiple Go Versions
How to install, use, and switch between multiple Go versions on the same machine for library testing, build reproducibility, and working with legacy projects
There is no rule that a developer machine must run only one Go toolchain. Library maintainers test against older releases, cloud SDKs sometimes lag behind, and continuous integration needs exact version matches. Go makes it straightforward to keep several versions side by side without containers or virtual machines, using either the official go install approach or the newer GOTOOLCHAIN mechanism.
Why You Might Need Multiple Go Versions
The Go compatibility promise means that code written for Go 1.x compiles on any later 1.x release almost always without changes. Despite this, there are situations where a single version is not enough.
You maintain an open‑source library. Clients may be stuck on older Go releases because of corporate policies or extended support windows. You cannot force them to upgrade, so you must verify that your library builds and passes tests on those versions.
You consume a cloud service whose Go SDK has not been updated for the newest compiler. Until the provider ships a compatible release, your entire project remains pinned to the version the SDK supports.
You work in a team that uses reproducible builds. Build systems hardcode a specific Go toolchain so that a given commit always produces bit‑identical binaries. On your local machine you need that exact version to reproduce and debug issues.
In each case, installing an extra version of Go – and switching between them – is the clearest path forward.
When you don't need multiple versions:
If you only ever use the latest stable release and none of the above applies, you can safely skip this page. Upgrading via your package manager or the official installer is enough.
Installing an Additional Version with go install
Go ships with a built‑in mechanism to fetch any released version as a separate, self‑contained installation. The process uses a lightweight wrapper binary that you call by a version‑specific name, like go1.21.6.
The only prerequisite is that you already have one working Go installation and that git is available on your PATH.
Step 1: Install the version wrapper
Run go install with the special module path golang.org/dl/goX.Y.Z@latest. Replace X.Y.Z with the exact version you want. This downloads a small Go program named go1.21.6 (or whatever version you chose) into $GOPATH/bin (or $GOBIN if set).
go install golang.org/dl/go1.21.6@latest
The command produces no output when it succeeds. The new binary is placed in the bin directory of your GOPATH.
Command not found after install:
If go1.21.6 is not recognised as a command later, your shell cannot find $GOPATH/bin. See the next section on configuring your PATH.
Step 2: Download the full toolchain
The wrapper does not yet contain the Go distribution. Run the versioned binary with the download subcommand to fetch and unpack the actual compiler and standard library.
go1.21.6 download
The files land in $HOME/sdk/go1.21.6 on Linux and macOS, or %USERPROFILE%\sdk\go1.21.6 on Windows. This directory becomes the GOROOT for that version.
Step 3: Verify the installation
Check that the freshly downloaded toolchain reports its version correctly.
go1.21.6 version
You should see output similar to go version go1.21.6 linux/amd64. You can also inspect its GOROOT with go1.21.6 env GOROOT.
Verification passed:
If the version string matches and the GOROOT points to the expected sdk directory, the installation is complete and ready to use.
Making Versioned Commands Available in Your Shell
The wrapper binary ends up in $(go env GOPATH)/bin. Unless that directory is already on your PATH, your shell will not find go1.21.6. Add the directory permanently so that any future version wrappers are immediately accessible.
Add the following line to your shell profile (~/.bashrc, ~/.zshrc, or ~/.profile). After editing, reload the file with source ~/.bashrc or open a new terminal.
export PATH="$(go env GOPATH)/bin:$PATH"
Do not overwrite the system go binary:
Resist the urge to rename go1.21.6 to go and place it earlier on your PATH. That makes it easy to accidentally build a project with the wrong toolchain and is not necessary once you use aliases or GOTOOLCHAIN.
Working with the Versioned Command
Once the wrapper is on your PATH, you can prefix any standard go command with the version number.
go1.21.6 build ./...
go1.21.6 test -race ./...
go1.21.6 env GOROOT
The versioned command uses its own GOROOT, GOPATH, module cache, and build cache. It does not interfere with your primary Go installation. This makes it safe to compile the same project with multiple versions side by side just by changing the command name.
To remove a version you no longer need, delete its SDK directory and the wrapper binary.
# Linux/macOS
rm -rf $(go1.21.6 env GOROOT)
rm $(which go1.21.6)
# Windows (PowerShell)
Remove-Item -Recurse -Force "$env:USERPROFILE\sdk\go1.21.6"
Remove-Item "$env:USERPROFILE\go\bin\go1.21.6.exe"
Automatic Toolchain Switching with GOTOOLCHAIN
Go 1.21 introduced a more automatic way to manage versions: the GOTOOLCHAIN environment variable and the toolchain directive in go.mod. Instead of manually typing go1.21.6 for every command, you can tell the standard go command to use a specific toolchain.
When GOTOOLCHAIN is auto (the default), the go command inspects the go and toolchain lines in a module’s go.mod file. If the local Go installation is older than what the module requires, Go automatically downloads and invokes the needed toolchain. The download happens once and is cached in GOPATH.
You can also pin a version manually:
# Pin the current project to Go 1.22.0
go env -w GOTOOLCHAIN=go1.22.0
# Or set it for a single command
GOTOOLCHAIN=go1.21.6 go build ./...
Setting GOTOOLCHAIN=local forces the use of whatever go binary is on your PATH, ignoring any toolchain directive. This is useful in CI environments that only have one version available and must fail fast if it is too old.
GOTOOLCHAIN and go install wrappers are complementary:
The GOTOOLCHAIN approach feels invisible once configured, but it requires Go 1.21 or later as the launcher. If you need an older version than 1.21 itself, you must still install it manually via golang.org/dl.
A go.mod that targets a newer toolchain looks like this:
module example.com/myapp
go 1.22.0
toolchain go1.22.3
With GOTOOLCHAIN=auto, running go build inside this module automatically downloads Go 1.22.3 if the system Go is older. No wrapper binary or PATH trickery is required.
Checking which toolchain is active:
Run go version inside your project directory. If GOTOOLCHAIN triggered a download, the version string reflects the active toolchain, not the launcher.
Switching Versions Conveniently in Practice
Even with GOTOOLCHAIN, there are workflows where you want a fast, explicit switch. Several community patterns have emerged.
Shell aliases. Define aliases so that the normal go command points to a specific version in a particular terminal session.
alias go='go1.21.6'
go version # reports go1.21.6
You can make this directory‑specific with tools like direnv, which runs a script when you cd into a project. Place an .envrc file containing alias go='go1.21.6' and direnv activates it automatically.
Third‑party version managers. Projects like gvm, goenv, g, and asdf‑golang offer richer interfaces, including per‑directory configuration and the ability to compile Go from source. They are not maintained by the Go team and each has its own trade‑offs. If your team already uses asdf for other languages, its Go plugin may feel natural. Otherwise, the official methods described above are the simplest and most reliable starting point.
Common Pitfalls
The wrapper binary cannot be found. This is almost always because $(go env GOPATH)/bin is missing from your PATH. Run go env GOPATH, confirm the bin subdirectory exists, and add it to your profile. Reload the shell or open a new terminal window afterwards.
go get is used instead of go install. In older tutorials you may see go get golang.org/dl/go1.10.7. Since Go 1.17, go get is only for adding dependencies to a module. Always use go install golang.org/dl/goX.Y.Z@latest to obtain the version wrapper.
Mixing GOROOTs. Each versioned Go command has its own GOROOT (inside sdk/goX.Y.Z). Do not set the GOROOT environment variable globally; let the toolchain detect it. A wrong global GOROOT can cause any go command to use the wrong standard library.
Assuming GOTOOLCHAIN=auto will never fetch. If your module’s go.mod contains a toolchain directive that is newer than your local Go, the go command will download that version automatically. This can be surprising the first time it happens in a corporate environment with restricted internet access. Set GOTOOLCHAIN=local to prevent automatic downloads.
Switching Go versions reliably is a skill that pays off the moment you need to bisect a compiler regression, validate a library against an older release, or reproduce a CI failure. The techniques here give you precise control without disrupting your primary development environment.