Go 1.27.3 and 1.26.10 against eMudhra bug: upgrade and rebuild commands on Ubuntu 24.04
Practical guide to upgrading to Go 1.27.3 or 1.26.10 on Ubuntu 24.04, recompiling binaries and verifying the compromise eMudhra CA block (CVE-2026-111943).
UptimeMag editorial team · 2 November 2026 · 5 min read

Anyone who read this morning's news already knows that Go 1.27.3 and 1.26.10 block eight intermediate eMudhra CAs with weak private keys (CVE-2026-111943). Here are the commands, tested on Ubuntu 24.04 LTS (noble), to move from the news to a patched machine.
1. Check what's running now
go version
If the line reports go1.27.2, go1.26.9 or any earlier version, the toolchain is vulnerable. But there's more important information: the version with which every binary in production was compiled, not the one installed on your build machine. Check it like this:
go version -m /path/to/binary
The -m flag prints embedded metadata, including the compiler version used. According to the official Go team blog, you can also use go version -m <path_to_your_binary> to verify the version a binary was compiled with, and binaries recompiled with Go 1.27.3 and 1.26.10 will automatically reject certificates issued by the compromised eMudhra intermediates.
2. Why Ubuntu 24.04's apt isn't enough
First practical rule, verified 2 November 2026 on the official Ubuntu repository: the golang-go package in noble is stuck at version 2:1.22~2build1, which depends on golang-1.22-go. An apt upgrade on Ubuntu 24.04 won't bring you to 1.27.3 or 1.26.10, simply because that version isn't in the standard repositories. Anyone managing servers on this distribution has two practical routes:
Official Canonical snap (convenient for those who want to stay up to date without managing tarballs manually):
sudo snap install go --classic
The latest/stable channel of the snap was at version 1.27.3 as of the 2 November 2026 check.
Official tarball from go.dev (convenient for CI and environments where you want to pin the exact version):
wget https://go.dev/dl/go1.27.3.linux-amd64.tar.gz
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf go1.27.3.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
The file, verified on the official go.dev/dl page on 2 November 2026, weighs 67 MB for the Linux x86-64 architecture. Those preferring to stay on the 1.26 branch download go1.26.10.linux-amd64.tar.gz (64 MB) instead, same procedure.
Those keeping multiple Go versions in parallel (to test code against different toolchains) can install an additional one without touching the system version:
go install golang.org/dl/go1.27.3@latest
go1.27.3 download
and then call it with go1.27.3 version instead of go version.
3. The point this morning's column had no space to explain: recompile, don't just upgrade
Blocking the eMudhra CAs isn't a runtime check read from a configuration file: it's compiled into the binary, in the crypto/x509 package. This means that:
- upgrading
goon the server changes nothing for already-compiled binaries already running; - a Go reverse proxy, a monitoring agent, a CLI tool written in Go: if they were compiled with Go before 1.27.3/1.26.10, they remain vulnerable until they are recompiled and redistribued, even if the machine has the new toolchain installed.
For each Go project in production, the minimum cycle is:
cd /path/to/project
go build -o appname .
go version -m ./appname
The last command must report go1.27.3 or go1.26.10 (or later version) in the go line of the metadata. If the project runs in containers, recompilation must be done without cache to prevent a Docker layer from reintroducing the old toolchain:
docker build --no-cache -t appname:patched .
and in the Dockerfile the base image (golang:1.27.3 or higher) must be explicitly updated, not left to a floating tag like golang:1.27 which might not have been republished by the image maintainer yet.
4. Test programme: verify that the rejection actually works
The most direct way to know whether a recompiled binary actually rejects the eMudhra intermediates isn't to trust the declared version, but to perform a negative test: a small TLS client that attempts to verify a test certificate signed by one of the blocked intermediates, and check that crypto/x509 returns an error.
package main
import (
"crypto/x509"
"encoding/pem"
"fmt"
"os"
)
func main() {
certPEM, err := os.ReadFile(os.Args[1])
if err != nil {
fmt.Println("file read error:", err)
os.Exit(1)
}
block, _ := pem.Decode(certPEM)
if block == nil {
fmt.Println("invalid PEM")
os.Exit(1)
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
fmt.Println("certificate parsing error:", err)
os.Exit(1)
}
roots, _ := x509.SystemCertPool()
opts := x509.VerifyOptions{Roots: roots}
if _, err := cert.Verify(opts); err != nil {
fmt.Println("verification failed (expected if this is one of the blocked intermediates):", err)
os.Exit(0)
}
fmt.Println("WARNING: verification succeeded, the certificate was accepted")
os.Exit(2)
}
Compiled with go build -o test-emudhra test.go after upgrading to 1.27.3/1.26.10, and run against a test certificate that closes the chain on one of the eight blocked intermediates, it must print the verification error and exit with code 0. If the programme prints "WARNING" and exits with code 2, the binary wasn't recompiled with the correct toolchain, or the test certificate isn't actually signed by one of the incriminated keys.
An operational detail: to obtain test certificates signed by the compromised keys you need to rely on test packages published by individual projects (for example Atlantis included in the version 0.48.2 changelog the explanation of the change, useful as a reference to understand what to expect from the patched behaviour) rather than generating them yourself: it's not the purpose of this guide to distribute compromised private keys, for obvious reasons.
5. Case NGINX/Go reverse proxy: the hidden vendored dependency
Those using a reverse proxy written in Go (not NGINX itself, which is C and isn't touched by this bug, but tools like Caddy, Traefik or custom internal proxies) must check not only the main binary, but the vendored dependencies that handle TLS on their own. The useful command:
go version -m /usr/local/bin/proxy-name | grep -i x509
If the proxy uses an alternative TLS library that doesn't go through the stdlib crypto/x509 (happens with some gRPC clients or third-party libraries that vendor their own implementation), the eMudhra intermediates block doesn't apply automatically: it must be verified case by case by reading the project's go.mod and looking for dependencies that import crypto/x509 with a pinned version different from that of the main module:
go list -m all | grep crypto
For pure NGINX, the issue shifts to the system trust store (package ca-certificates), which is a different and non-Go-specific intervention: it's not covered in this guide, but must still be verified separately if NGINX acts as a TLS client towards external backends.
Commands summary
| Action | Command | Environment verified |
|---|---|---|
| Check system Go version | go version |
Ubuntu 24.04 |
| Check Go version of a binary | go version -m <binary> |
Ubuntu 24.04 |
| Install Go via snap | sudo snap install go --classic |
snap, latest/stable channel, verified 2 November 2026 at 1.27.3 |
| Install Go via tarball | tar -C /usr/local -xzf go1.27.3.linux-amd64.tar.gz |
go.dev/dl, verified 2 November 2026 |
| Scan a binary | govulncheck -mode=binary <binary> |
command from Go Security team |
The last command in the table requires govulncheck to be installed first with the correct pinned toolchain, as indicated by the official Go blog: GOTOOLCHAIN=go1.27.3+auto go install golang.org/x/vuln/cmd/govulncheck@latest, followed by govulncheck -mode=binary <path_to_binary>.
Written with the help of artificial intelligence and checked by the editors (EU AI Act, art. 50).