Install Go on macOS Linux and Windows the Right Way
Install Go from the official archive or package, verify the download, and confirm the toolchain works. Covers macOS, Linux and Windows plus the PATH trap.

Most failed Go installs are not failed downloads. They are a second copy of Go sitting earlier on your PATH, answering go version with a release you thought you had replaced. The install itself is three commands on any platform. The part worth slowing down for is making sure the copy you installed is the one your shell actually runs, which is what I check at the end. Once it works, your first Go program is the natural next step, and the Go basics hub shows where it leads.
Tested with Go 1.25.6 on macOS arm64 (Homebrew), and the Go 1.27.3 Linux arm64 archive in an Alpine 3.22 container. The Windows steps follow go.dev/doc/install and were not run for this article.
Use the official download, not your distribution's package
Go ships a new minor release roughly every six months, and the project supports the two newest. A package from apt or dnf is usually pinned to whatever was current when your distribution froze, so you end up compiling against a release the Go team has already stopped patching. That is fine for a build server that someone else configured. For a development machine I would take the archive from go.dev/dl every time.
Two details make the official download pleasant. Each file has its SHA-256 checksum next to it on the page, and the whole page is also available as JSON (https://go.dev/dl/?mode=json), which is what you want if you script the install. And the toolchain is self-contained: it needs no installer database, no registry entries beyond a PATH change, and removing it is deleting one directory.
Linux: extract to /usr/local
The official instructions on go.dev/doc/install are the ones below, and I ran them end to end in a clean Alpine container to check them. The checksum line is not in the official instructions. I add it because it costs one command and catches a truncated download, which otherwise shows up later as a confusing gzip: invalid header.
curl -fsSLO https://go.dev/dl/go1.27.3.linux-arm64.tar.gz
echo 'a06e99309dbcbdca1a8ccd2282c3ee146e55930886e26b4f1193269587165f49 go1.27.3.linux-arm64.tar.gz' | sha256sum -c -
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.27.3.linux-arm64.tar.gz
export PATH=$PATH:/usr/local/go/bin
go versionThis is what the container printed, with the checksum line first:
go1.27.3.linux-arm64.tar.gz: OK
go version go1.27.3 linux/arm64Swap the file name for linux-amd64 on an Intel or AMD machine, and use whatever release go.dev/dl shows as newest on the day you read this. The rm -rf /usr/local/go matters. Extracting a new release on top of an old one leaves behind files that the new release deleted, and those stale files can break a build in ways that look like a compiler bug. Delete first, then extract.
The export only lasts for that shell. To keep it, add the line to ~/.profile or ~/.zshrc, then open a new terminal. The unpacked toolchain was 277 MB on disk in my run, which is worth knowing if you are building a small image.
macOS: the package, or Homebrew
The .pkg from the download page installs to /usr/local/go and adds /usr/local/go/bin to your path through /etc/paths.d/go. Open a new terminal afterwards, because a running shell does not re-read it. Pick arm64 for Apple Silicon and amd64 for an Intel Mac. If you are not sure, uname -m prints arm64 or x86_64.
Homebrew works too, and it is what I ran for this article: brew install go. The binary lands under the Homebrew prefix, which go printed /opt/homebrew/bin/go here, and go env GOROOT pointed at the Cellar path. Homebrew follows the newest release, so a later brew upgrade can move you to a new minor version without asking. If a project must stay on one release, use the package or let go.mod pin the toolchain, which comes up below.
Windows: the MSI
Download the .msi for amd64 (or arm64 on a Windows on Arm laptop), run it and accept the defaults. It installs under Program Files in a folder named Go and sets PATH for you, but again only for terminals opened afterwards. I did not run the Windows installer for this article, so I am relying on the official instructions there. Check the result in a new PowerShell window with go version.
Two Windows details cost people time. If a corporate machine blocks the MSI, the .zip archive works as a plain extract, and you add its bin directory to PATH yourself. And if you use WSL, install Go inside the Linux distribution with the Linux steps. A Windows go.exe and a WSL go are separate installs that do not share a module cache.
Verify that the right Go is the one running
This is the check that matters. Run these three commands:
go version
which go
go env GOROOT GOPATH GOBINOn Windows, where go replaces which go. On the Alpine container they printed:
go version go1.27.3 linux/arm64
/usr/local/go
/root/go
/root/go/binThe GOROOT line is where the toolchain itself lives. GOPATH is where downloaded modules and tools you install end up, and GOBIN is where go install puts binaries (by default GOPATH/bin). Notice that GOPATH/bin is not the same as /usr/local/go/bin. A tool you install with go install is invisible until you add the second directory to your PATH:
export PATH=$PATH:$(go env GOPATH)/binIf which go shows a path you did not expect, that is your second Go. Remove the one you do not want, or fix the order of directories in PATH. Do not install again; reinstalling does not change which copy wins.
Prove it builds something
A version string proves the binary starts. It does not prove the toolchain can compile and link, which needs write access to the module and build caches. Make a throwaway module and run a program:
mkdir hello && cd hello
go mod init example.test/hellopackage main
import "fmt"
func main() {
fmt.Println("hello")
}go run .On my Linux container that printed hello, and go mod init wrote a module file. The go line in it is worth reading once. On macOS with Go 1.25.6, the generated go.mod was:
module example.test/hello
go 1.25.6That line says the minimum Go release this module needs. It is what makes the next part work.
Newer toolchains arrive on demand
Since Go 1.21, the go command treats the go line in go.mod as a requirement. If you check out a project that says go 1.27.3 and your installed Go is older, the command downloads that toolchain into the module cache and runs it, rather than failing with a cryptic syntax error. The mechanism is documented in Go Toolchains, and the switch is the GOTOOLCHAIN setting, which defaults to auto. Setting GOTOOLCHAIN=local turns it off, which is what I did while testing this site's code so that every sample ran on the installed release and nothing quietly upgraded.
The practical result: you no longer need a version manager just to follow projects at different Go versions. You need one good install, and the project's go.mod decides the rest. If you do want several full installs side by side, go install golang.org/dl/go1.25.6@latest gives you a go1.25.6 command that downloads and runs that exact release next to your main one.
Where this install advice stops
If your team builds in containers, the official golang image is a better answer than a host install, and the host Go is only for your editor. If you need a locked release on a managed workstation, use the package your IT team approves and let GOTOOLCHAIN handle the rest. And if you only want to try the language for ten minutes, the Go Playground at go.dev/play runs a program in a browser with nothing installed at all, though it runs without network access, so it is not enough once you need a dependency. The go.mod and module story is the next thing worth reading once the toolchain works.


