Athens Go Module Proxy 0.20.0
Overview
Athens is a Go module proxy. It speaks the GOPROXY protocol, so go build, go mod download and go get fetch modules from it instead of from the internet. The first request for a module version downloads it from proxy.golang.org, or straight from its git repository when it is not there, and stores it. Every later request is served from your own instance. Builds keep working when an upstream repository is deleted or unreachable, and your CI fleet stops pulling the same modules from the internet over and over.
This guide covers the Athens Go Module Proxy 0.20.0 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud artifact and container registries listing on AWS Marketplace. You pick it as the software version when you subscribe and launch.
Upstream Athens listens on every interface with no authentication and no TLS, and it fetches any module path a client asks for, including paths that name hosts inside your network. The SecureAnyCloud image turns it into a proxy that you can safely place in a VPC.
What you get:
- TLS and a password from the first boot. nginx serves the proxy on port 443 (
https://<host>/) with a certificate generated on your instance's first boot for its DNS name and IP addresses. Every proxy request needs HTTP basic authentication with the usergoproxyand a password generated on that first boot and written to/root/athens-credentials.txt(root only). Only the/healthzhealth check is open. Athens itself listens on127.0.0.1only, and nothing listens on port 80. - An egress firewall for the proxy. Clients choose module paths, so a path such as
10.0.0.5/xwould make an unprotected proxy connect to an internal address. On this image, the kernel refuses every connection that Athens, and thegoandgitprocesses it runs, opens to link-local, metadata, carrier-grade NAT or private (RFC 1918) addresses. The service's own cgroup adds a second, independent block. The rules load before Athens on every boot, and Athens refuses to start without them. - Checksums verified. Downloads are verified against the Go checksum database (
GOSUMDB=sum.golang.org). No module is exempt by default, and Athens also proxies the checksum database to your clients. - Your modules on their own disk. The module store lives on a separate 30 GiB gp3 data volume mounted at
/var/lib/athens, which you can resize independently. - Locked down. Athens runs as an unprivileged user in a sandboxed service. Athens, nginx and the first-boot service cannot reach the instance metadata service, and the AMI requires IMDSv2. Requests for host names that the instance does not serve are dropped.
- Built from source. Athens 0.20.0 is compiled from the upstream release (pinned by SHA-256) with the pinned Go 1.27.3 toolchain. Its dependencies are raised to versions that fix the published advisories in the upstream set. The base is fully patched Ubuntu 24.04 LTS with unattended security upgrades switched on.
What is on the image: Athens 0.20.0 (/opt/athens, with licence and build provenance in /usr/share/doc/athens), Go 1.27.3 at /usr/local/go (Athens runs go mod download for every new module), git, nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.
Default user: ubuntu. Architecture: x86_64. Recommended instance type: m5.large.
Prerequisites
Before you launch, make sure you have:
- An AWS account subscribed to the listing, an EC2 key pair, and a VPC subnet that your build machines can reach.
- Outbound HTTPS from the instance to the internet (proxy.golang.org, sum.golang.org and the git hosts of the modules you use).
- A security group that allows TCP 22 (SSH) from your own networks only, and TCP 443 from your build machines and developers. Nothing else needs to be open.
Launch the instance
In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick Athens Go Module Proxy 0.20.0 on Ubuntu 24.04 as the software version, choose your Region, and select Launch through EC2.
Choose m5.large, your key pair, and a security group as described above. The AMI attaches the 30 GiB data volume for /var/lib/athens automatically. If you build against many modules, increase its size in the storage section of the launch wizard.
To launch with the AWS CLI instead, replace the values in angle brackets:
aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
--instance-type m5.large --key-name <your-key-pair> \
--security-group-ids <your-security-group> --subnet-id <your-subnet> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=athens}]'Connect to your instance
Connect over SSH as the ubuntu user, the default login user for Athens Go Module Proxy 0.20.0 on Ubuntu 24.04.
ssh -i <your-key.pem> ubuntu@<public-ip>Check the services
First boot generates the certificate and the proxy password. The egress firewall, Athens and nginx then start. Confirm that all three services are active and that first boot has finished:
systemctl is-active athens nginx sac-athens-egress
sudo test -f /var/lib/sac/data/athens-firstboot.done && echo FIRSTBOOT_DONEactive active active FIRSTBOOT_DONEOnly SSH and nginx listen on the network. Athens listens on loopback behind nginx, and the DNS resolver listens on loopback only:
sudo ss -lntp | awk 'NR>1 {print $4, $6}' | sed -E 's/pid=[0-9]+,fd=[0-9]+/.../g' | sort0.0.0.0:22 users:(("sshd",...),("systemd",...)) 0.0.0.0:443 users:(("nginx",...),("nginx",...),("nginx",...)) 127.0.0.1:3000 users:(("athens",...)) 127.0.0.53%lo:53 users:(("systemd-resolve",...)) 127.0.0.54:53 users:(("systemd-resolve",...)) [::]:22 users:(("sshd",...),("systemd",...)) [::]:443 users:(("nginx",...),("nginx",...),("nginx",...))Confirm that the module store is on its own volume:
df -h /var/lib/athens | awk 'NR==2 {print $2, $6}'30G /var/lib/athensGet your proxy URL and password
The credentials file is readable by root only. Run the following command to print it with the password hidden. To see the password itself, run sudo cat /root/athens-credentials.txt.
sudo sed -E 's/^(ATHENS_PROXY_PASSWORD=).*/\1<hidden>/' /root/athens-credentials.txt# Athens Go Module Proxy 0.20.0 - generated on first boot by athens-firstboot.service
# This password is unique to this instance; none was baked into the image. Keep this file secret.
# Every proxy request needs it (HTTP basic auth); only /healthz is open. ATHENS_PROXY_URL=https://ec2-44-204-161-253.compute-1.amazonaws.com/ ATHENS_HOST=ec2-44-204-161-253.compute-1.amazonaws.com ATHENS_PROXY_USER=goproxy ATHENS_PROXY_PASSWORD=<hidden> ATHENS_CA_CERT=/etc/nginx/tls/athens.crt ATHENS_CERT_SHA256=F4:E5:5A:AE:6F:86:95:9F:BD:AD:B1:59:A1:72:57:47:DB:0B:D6:EF:EE:C3:2E:55:23:FB:E7:7B:C7:E7:94:A9
#
# Go clients: put the password in ~/.netrc (machine <ATHENS_HOST> login goproxy password <ATHENS_PROXY_PASSWORD>),
# then:
export GOPROXY=https://<ATHENS_HOST>/ GOSUMDB=sum.golang.org SSL_CERT_FILE=<a copy of athens.crt>
# The certificate is self-signed: trust /etc/nginx/tls/athens.crt on your clients, or use your own (see the guide).
# New proxy password:
sudo athens-rotate-password
# Change the name it serves as:
sudo athens-set-hostname <your-dns-name>
# User guide: https://secureanycloud.com/stacks/athens-go-module-proxy-0-20-0/Your instance shows its own DNS name and certificate fingerprint. To serve the proxy under your own name, see Step 8.
Fetch a module through the proxy
Ask the proxy for the versions of github.com/google/uuid, first without the password and then with it. The command passes the password to curl on standard input, so it never appears in the process list:
sudo bash -c ' P=$(grep "^ATHENS_PROXY_PASSWORD=" /root/athens-credentials.txt | cut -d= -f2-) C=/etc/nginx/tls/athens.crt curl -s --cacert $C -o /dev/null -w "no password -> HTTP %{http_code}\n" https://localhost/github.com/google/uuid/@v/list printf "user = \"goproxy:%s\"\n" "$P" | curl -K - -s --cacert $C https://localhost/github.com/google/uuid/@v/list | tail -3; echo printf "user = \"goproxy:%s\"\n" "$P" | curl -K - -s --cacert $C https://localhost/github.com/google/uuid/@v/v1.6.0.info; echo'no password -> HTTP 401 v1.4.0 v1.5.0 v1.6.0 {"Version":"v1.6.0","Time":"2024-01-23T18:54:04Z","Origin":{"VCS":"git","URL":"https://github.com/google/uuid","Hash":"0f11ee6918f41a04c201eceeadf612a377bc7fbc","Ref":"refs/tags/v1.6.0"}}When a version is first requested, Athens fetches it, checks it against sum.golang.org and stores the .info, .mod and .zip files under /var/lib/athens/storage. Every later request for that version is served from there.
Use the proxy from your build machines
Copy the instance's certificate (/etc/nginx/tls/athens.crt) to your build machine and compare its fingerprint with ATHENS_CERT_SHA256 from Step 4.
It is a leaf server certificate for this instance's names only (CA:FALSE, no certificate signing). Trusting it lets that machine trust this proxy and nothing else. On an Ubuntu or Debian build machine, run:
scp -i <your-key.pem> ubuntu@<public-ip>:/etc/nginx/tls/athens.crt ./athens.crt openssl x509 -noout -fingerprint -sha256 -in ./athens.crt
sudo install -m 0644 ./athens.crt /usr/local/share/ca-certificates/athens.crt
sudo update-ca-certificatesThe go command reads basic-auth credentials for a proxy from ~/.netrc (or the file named by NETRC). On the build machine, replace <your-domain> with your instance's DNS name (or your own, from Step 8) and use the password from Step 4:
( umask 077; printf 'machine <your-domain> login goproxy password %s\n' "$ATHENS_PROXY_PASSWORD" >> ~/.netrc ) export GOPROXY=https://<your-domain>/ GOSUMDB=sum.golang.org go mod downloadIn CI, keep the password in the pipeline's secret store and write the .netrc at the start of the job, rather than keeping it in a file in the repository. For a developer machine, go env -w GOPROXY=https://<your-domain>/ makes the setting permanent.
To try a first go get through the proxy, run the following in a new module in an empty directory on a build machine:
export GOPROXY=https://<your-domain>/ GOSUMDB=sum.golang.org go mod init example.com/demo go get golang.org/x/text@v0.43.0 go env GOPROXY GOSUMDBgo: downloading golang.org/x/text v0.43.0 go: added golang.org/x/text v0.43.0 https://<your-domain>/ sum.golang.orgPrivate modules. The image holds no git credentials, and its egress firewall does not let Athens reach git servers on private addresses, so it serves public modules.
For your own private modules, set GOPRIVATE=<your-git-host>/* on your build machines. The go command then fetches those modules directly with your own git credentials, skipping the proxy and the public checksum database for them only. Leave GOSUMDB on for everything else.
Check the security posture
Everything except the health check needs the password. A request for a host name that the instance does not serve is dropped without an answer, and nothing answers on port 80. Verify this with:
C=/etc/nginx/tls/athens.crt curl -s --cacert $C -o /dev/null -w 'catalog, no password -> HTTP %{http_code}\n' https://localhost/catalog curl -s --cacert $C -K <(printf 'user = "goproxy:not-the-password"\n') -o /dev/null -w 'wrong password -> HTTP %{http_code}\n' https://localhost/github.com/google/uuid/@v/list curl -s --cacert $C -o /dev/null -w 'health check -> HTTP %{http_code}\n' https://localhost/healthz curl -s --cacert $C -H 'Host: evil.example' -o /dev/null -w 'unknown host -> %{http_code}\n' https://localhost/healthz || true curl -s -o /dev/null -w 'plain http :80 -> %{http_code}\n' --connect-timeout 3 http://127.0.0.1/ || truecatalog, no password -> HTTP 401 wrong password -> HTTP 401 health check -> HTTP 200 unknown host -> 000 plain http :80 -> 000000 means no answer: nginx closes the connection for a host name it does not serve, and nothing listens on port 80.
The certificate is a leaf certificate for the instance's names. Inspect it with:
openssl x509 -noout -ext subjectAltName,basicConstraints,keyUsage,extendedKeyUsage -in /etc/nginx/tls/athens.crtX509v3 Subject Alternative Name: DNS:ec2-44-204-161-253.compute-1.amazonaws.com, IP Address:44.204.161.253, IP Address:172.31.93.135, IP Address:127.0.0.1, DNS:localhost X509v3 Basic Constraints: critical CA:FALSE X509v3 Key Usage: critical Digital Signature, Key Encipherment X509v3 Extended Key Usage: TLS Web Server AuthenticationThe egress firewall applies to every process running as the athens user. Loopback is allowed. Link-local (including the metadata service), carrier-grade NAT, private and 0.0.0.0/8 addresses are refused, with the same ranges for IPv6 (unique local, link-local, NAT64 and IPv4-mapped). List the rules with:
sudo iptables -S OUTPUT | grep SAC
sudo iptables -S SAC-ATHENS-EGRESS | grep -E '^-A' | grep -v icmp-A OUTPUT -m owner --uid-owner 997 -j SAC-ATHENS-EGRESS -A SAC-ATHENS-EGRESS -d 127.0.0.0/8 -o lo -j RETURN -A SAC-ATHENS-EGRESS -d 169.254.0.0/16 -p tcp -j REJECT --reject-with tcp-reset -A SAC-ATHENS-EGRESS -d 100.64.0.0/10 -p tcp -j REJECT --reject-with tcp-reset -A SAC-ATHENS-EGRESS -d 10.0.0.0/8 -p tcp -j REJECT --reject-with tcp-reset -A SAC-ATHENS-EGRESS -d 172.16.0.0/12 -p tcp -j REJECT --reject-with tcp-reset -A SAC-ATHENS-EGRESS -d 192.168.0.0/16 -p tcp -j REJECT --reject-with tcp-reset -A SAC-ATHENS-EGRESS -d 0.0.0.0/8 -p tcp -j REJECT --reject-with tcp-resetRefused connections are reset at once (REJECT) rather than silently dropped, so a module path that names an internal address fails immediately instead of hanging a build.
Try it as the athens user. The metadata service and a private address are refused, and the public proxy is reachable:
for t in http://169.254.169.254/latest/meta-data/ http://10.0.0.1/ https://proxy.golang.org/; do if code=$(sudo setpriv --reuid=athens --regid=athens --clear-groups -- curl -s -m 5 -o /dev/null -w '%{http_code}' "$t"); then echo "$t -> HTTP $code"; else echo "$t -> refused"; fi donehttp://169.254.169.254/latest/meta-data/ -> refused http://10.0.0.1/ -> refused https://proxy.golang.org/ -> HTTP 200Athens runs as the unprivileged athens user. Its service also carries the same ranges as a cgroup IPAddressDeny list, a second block that does not depend on iptables. nginx and the first-boot service cannot reach the metadata service. Confirm this with:
ps -o user:8=,args= -C athens systemctl show athens nginx athens-firstboot -p Id -p IPAddressDenyathens /opt/athens/athens -config_file=/etc/athens/config.toml IPAddressDeny=0.0.0.0/8 169.254.0.0/16 64:ff9b:1::/48 64:ff9b::/96 172.16.0.0/12 ::ffff:0.0.0.0/96 192.168.0.0/16 fc00::/7 10.0.0.0/8 100.64.0.0/10 168.63.129.16/32 fe80::/10 Id=athens.service IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128 Id=nginx.service IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128 Id=athens-firstboot.servicesac-athens-egress.service applies the rules in one atomic iptables-restore transaction per address family. A reload replaces the chain in a single step, a reload that fails leaves the previous rules in place, and stopping the unit does not remove them. Run sudo systemctl reload sac-athens-egress to re-apply the rules.
Rotate the password and use your own DNS name
sudo athens-rotate-password replaces the proxy password with a new random one, writes it to /root/athens-credentials.txt and reloads nginx. The old password stops working at once.
Afterwards, update the .netrc on your build machines and your CI secret store. Rotate the password whenever it may have been exposed.
To serve the proxy under your own name, point a DNS record at the instance, then set the name the proxy is served as. This mints a new self-signed leaf certificate for the name (and the instance's IP addresses), updates nginx and the URLs in the credentials file, and keeps the name across reboots. Give your build machines the new certificate afterwards:
sudo athens-set-hostname <your-domain>To use a certificate from your own certificate authority instead, copy it over the generated pair and reload nginx. Running athens-set-hostname again replaces it with a self-signed one.
sudo install -m 0644 <your-domain>.crt /etc/nginx/tls/athens.crt
sudo install -m 0600 <your-domain>.key /etc/nginx/tls/athens.key
sudo nginx -t && sudo systemctl reload nginxServer components
Component Path
Athens application /opt/athens/athens (read only, owner root)
Licence and build provenance /usr/share/doc/athens/
Configuration /etc/athens/config.toml
Module store /var/lib/athens/storage (data volume)
Go toolchain used by Athens /usr/local/go (Go 1.27.3)
Service units athens.service, sac-athens-egress.service, athens-firstboot.service in /etc/systemd/system/
Egress rules /usr/local/sbin/athens-egress-apply (--print shows the ruleset)
nginx site and password file /etc/nginx/sites-available/sac-athens, /etc/nginx/athens.htpasswd
Certificate and key /etc/nginx/tls/
Helpers /usr/local/sbin/athens-rotate-password, athens-set-hostname, athens-guard
Credentials /root/athens-credentials.txt (mode 0600)
/opt/athens/athens -version | head -3 grep -E '^(source|toolchain|changes)' /usr/share/doc/athens/BUILD-PROVENANCE.txt nginx -v 2>&1Build Details: Version: v0.20.0 Date: 2026-11-07-14:42:07-UTC source: https://github.com/gomods/athens/archive/refs/tags/v0.20.0.tar.gz toolchain: go1.27.3 linux/amd64 (https://go.dev/dl/go1.27.3.linux-amd64.tar.gz, sha256 65efbc80fdc1fa2cd2631cad5b481777a68e8725f34e89f50fc6ac714e379b40) changes: dependency raise only (go.mod, no source patch): github.com/klauspost/compress@v1.20.2 golang.org/x/net@v0.62.0 go.etcd.io/etcd/client/pkg/v3@v3.6.14 nginx version: nginx/1.24.0 (Ubuntu)These are the settings that matter most:
grep -E '^(GoBinaryEnvVars|StorageType|Port|SumDBs|NoSumPatterns|GoDownloadEnabled|DownloadMode|NetworkMode)' /etc/athens/config.tomlGoBinaryEnvVars = ["GOPROXY=https://proxy.golang.org,direct", "GOSUMDB=sum.golang.org", "GOTOOLCHAIN=local", "GOVCS=*:git", "GIT_TERMINAL_PROMPT=0", "GIT_ALLOW_PROTOCOL=https"] StorageType = "disk" Port = "127.0.0.1:3000" SumDBs = ["https://sum.golang.org"] NoSumPatterns = [] GoDownloadEnabled = false DownloadMode = "sync" NetworkMode = "strict"Athens fetches new modules from proxy.golang.org first, and directly from git (over HTTPS only) when a module is not there. It verifies them against sum.golang.org.
To change a setting, edit /etc/athens/config.toml using the names from the Athens configuration documentation, then run sudo systemctl restart athens. For logs, use sudo journalctl -u athens for Athens and /var/log/nginx/access.log for requests, which include the real client address.
Security recommendations
- Restrict network access. Allow port 22 in the security group from your own networks only, and port 443 only from the networks of your build machines and developers.
- Protect the proxy password. Keep it in your CI system's secret store, and rotate it (Step 8) when someone leaves or it may have been exposed.
- Keep checksum verification on. Leave
GOSUMDBon in your clients, and useGOPRIVATEonly for your own private module paths. - Back up the module store. If your builds rely on modules that could disappear upstream, back up
/var/lib/athens. An EBS snapshot of the data volume is simplest. - Use your own name and certificate. See Step 8.
- Keep the operating system patched. Unattended security upgrades are on, and
sudo apt-get update && sudo apt-get upgradeapplies everything else.
Support and licensing
Athens is open source under the MIT License, and there is no per-user or per-CPU licence fee. The licence is in /usr/share/doc/athens/. The AMI price covers SecureAnyCloud's hardening, first-boot setup and support.
SecureAnyCloud is not affiliated with or endorsed by the Athens project or the Go project.
- Email: sales@secureanycloud.com
- Website: https://secureanycloud.com
- Support: 24/7, with a 24-hour response SLA
Deploy on AWS
Launch Athens Go Module Proxy 0.20.0 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com