G3 Proxy 1.12.4
Overview
This guide covers launching and verifying G3 Proxy 1.12.4 on Ubuntu 24.04 from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud Hardened WireGuard VPN & VPN, Proxy and Remote Access AMIs listing on AWS Marketplace. You pick it as the software version when you subscribe and launch. The other VPN, proxy and remote access servers in the listing are described on the listing's guide hub.
G3 Proxy (g3proxy) is the forward proxy of ByteDance's open-source G3 project, written in Rust. Clients send their web traffic to it with the standard HTTP proxy protocol. Plain http:// requests are forwarded, and https:// sites go through a CONNECT tunnel, so the TLS session stays end to end between the client and the site. A typical use is giving instances in private subnets controlled, authenticated web access (package updates, API calls) through one audited exit point.
SecureAnyCloud compiles g3proxy 1.12.4 on this image from the upstream g3proxy-v1.12.4 release tag with Rust 1.99.1. Six Rust dependencies (h2, rustls, rustls-webpki, webpki-roots, quinn-proto and tokio) are raised to patched versions inside the ranges that upstream's own Cargo.toml allows. This clears their RustSec advisories without changing any g3 source file. The build includes only the features this configuration uses (the c-ares resolver and rustls with ring; no Lua, Python or QUIC). The file /usr/share/doc/g3proxy/BUILD-PROVENANCE.txt lists every pinned input and the checksum of the installed binary.
What you get:
- g3proxy 1.12.4 (
g3proxy.service): the HTTP/HTTPS forward proxy on TCP 3128, for clients in your VPC, with authentication required - Credentials of its own: the proxy user and its 32-character password are generated on your instance's first boot, and g3proxy stores only a SHA-512 crypt hash of the password
- A helper,
g3proxy-selftest, which runs an end-to-end check and prints no secret
Default user: ubuntu. Architecture: x86_64. Root volume: 8 GiB gp3.
Security posture
- Not an open proxy. Every request must carry the instance's proxy user and password (HTTP Basic proxy authentication). A request without them, with a wrong password or for an unknown user gets HTTP 407. First boot generates the user (
proxy-plus 8 random hex characters) and a 32-character password, stores only a SHA-512 crypt hash in/etc/g3proxy/users.json, and writes the clear password to/root/g3proxy-credentials.txt, readable only by root. No default or shared credential exists. - Only your VPC can connect. First boot reads your VPC's CIDR blocks from the instance metadata service (IMDSv2) and allows clients from those blocks and from the instance itself. A connection from any other address is closed before authentication. The default security group of the listing opens SSH (22) only, so you open TCP 3128 to the clients that should use the proxy (Step 1).
- Only web destinations, never private ones. Requests may go to ports 80 and 443 only, for plain forwarding and for
CONNECTalike. Other ports (SSH, SMTP and so on) and FTP over HTTP are refused. The proxy refuses loopback, the instance metadata service (169.254.169.254 and fd00:ec2::254), link-local, private (RFC 1918) and carrier-grade NAT ranges, your VPC's CIDR blocks and the instance's own addresses. The check runs after the destination name is resolved, so a host name that resolves to one of those addresses (localhost,instance-data, a public DNS name pointing at 127.0.0.1, or an encoded address such as0x7f000001) is refused too. Step 7 shows how to allow private destinations if you need them. - No SSRF to instance credentials. On top of the proxy's own refusal,
g3proxy.servicecarries anIPAddressDenyrule for the metadata addresses (169.254.169.254, fd00:ec2::254, 168.63.129.16), so the proxy process cannot reach the instance metadata service at all. The AMI is registered IMDSv2 only. - Least privilege.
g3proxyruns as the unprivileged g3proxy user with no capabilities, a read-only system (ProtectSystem=strict) and no access to home directories. Its control socket is a local file in/run/g3proxy, readable only by root and g3proxy. Nothing else listens on the network. - Fails closed.
g3proxy.servicestarts only after first boot has written its ready marker, and every start runs a guard (g3proxy-guard) that refuses a configuration first boot did not render, a user without a SHA-512 hash, an anonymous user, authentication switched off, a client list open to everyone, or a destination filter that no longer refuses loopback and link-local addresses. If first boot cannot finish, the proxy stays stopped and the next boot starts over. - Secrets stay out of the logs. The password is hashed with openssl passwd -6 -stdin and handed to curl through standard input, never on a command line. SecureAnyCloud checked that the password and its hash appear in no log file and not in the journal.
- Per-instance identity. SSH host keys and the machine ID are generated fresh on every instance.
- Security notes. Running cargo audit on the compiled binary (its dependency list is embedded with cargo-auditable, and the result ships as /usr/share/doc/g3proxy/cargo-audit-bin.json) reports three advisories with no fix in the versions upstream allows. None is reachable in this configuration. RUSTSEC-2025-0143 (capnp) is used only on the local control socket. RUSTSEC-2026-0119 (hickory-proto) concerns a resolver this configuration does not use, because it resolves with c-ares. RUSTSEC-2026-0118 (hickory-proto DNSSEC validation) is not compiled in. g3proxy 1.12.4 is the last release of the 1.12 long-term-support line. The G3 project is in maintenance mode, and its development continues as VEY. SecureAnyCloud tracks both and publishes new versions in this listing when updates appear.
Prerequisites
- An AWS account and permission to launch EC2 instances
- An EC2 key pair in the Region you launch in
- A VPC whose instances should use the proxy, which they reach on the proxy's private address
- A route to the internet for the proxy itself (a public subnet with a public IP, or a NAT gateway)
Recommended instance type: m5.large. g3proxy is light, so size the instance for the traffic your clients send through it.
Connecting to your instance
G3 Proxy 1.12.4 on Ubuntu 24.04 runs on Ubuntu 24.04 LTS. The SSH user is ubuntu.
Launch the instance
Option A: AWS Marketplace console
- Open the SecureAnyCloud Hardened WireGuard VPN & VPN, Proxy and Remote Access AMIs listing on AWS Marketplace and choose View purchase options, then Subscribe. One subscription covers every version in the listing.
- Once the subscription is active, choose Launch your software.
- Choose Launch from EC2 Console. In Version, choose G3 Proxy 1.12.4 on Ubuntu 24.04, then choose your Region.
- Pick an instance type and your key pair.
m5.largeis recommended, and the software charge is $0.04 per vCPU per hour. - In the security group, allow SSH (22) from your own IP address only, and TCP 3128 only from the clients that should use the proxy (a subnet CIDR or, better, the security group of those clients). Never open 3128 to
0.0.0.0/0. The proxy refuses clients outside your VPC anyway, and an open port only invites scans. - Choose a subnet from which the instance can reach the internet, and leave the metadata defaults, because the image already requires IMDSv2. Then choose Launch.
Option B: AWS CLI
Run these commands on your own workstation. They create a security group that allows SSH from your management address and the proxy port from your clients' security group only, and then launch the instance. Replace the AMI ID with the one shown for your Region on the Marketplace launch page. Replace the VPC, subnet, key pair, address and client security group with your own:
SG=$(aws ec2 create-security-group --group-name g3proxy \
--description "G3 Proxy" --vpc-id vpc-0123456789abcdef0 \
--query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 22 --cidr <your-mgmt-cidr>
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 3128 --source-group sg-0client0000000000
aws ec2 run-instances \
--image-id ami-0123456789abcdef0 \
--instance-type m5.large \
--key-name my-key-pair \
--security-group-ids "$SG" \
--subnet-id subnet-0123456789abcdef0 \
--associate-public-ip-address \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=g3proxy}]'You do not need --metadata-options, because the AMI already makes IMDSv2 required.
Connect and read your proxy details
Connect as ubuntu with the key pair you chose:
ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip>First boot finishes within a few seconds of launch. It reads your VPC's address ranges, writes the proxy configuration, creates the user and password, starts g3proxy, and proves that it refuses requests without the password.
The details are in /root/g3proxy-credentials.txt, readable only by root. This command shows everything except the password:
sudo grep -v '^PROXY_PASSWORD=' /root/g3proxy-credentials.txt# G3 Proxy 1.12.4 (g3proxy) - generated on first boot by g3proxy-firstboot.service.
# The user and password are unique to this instance; nothing was baked into the image. Keep this file private.
# g3proxy stores only a SHA-512 crypt hash of the password (/etc/g3proxy/users.json). PROXY_USERNAME=proxy-664bf978
# HTTP/HTTPS forward proxy (HTTP proxy protocol; HTTPS sites use CONNECT): this instance's private address, port 3128 PROXY_HOST=172.31.94.247 PROXY_PORT=3128 PROXY_URL=http://172.31.94.247:3128 # Clients allowed (before authentication): loopback and this VPC ALLOWED_CLIENTS=172.31.0.0/16 # Destinations: ports 80 and 443 only; loopback, link-local (instance metadata), private ranges, this VPC and this # instance are refused (see /etc/g3proxy/main.yml and the guide to change that). # # Example from a client in this VPC (open TCP 3128 in this instance's security group to that client only):
# curl --proxy http://172.31.94.247:3128 --proxy-user 'proxy-664bf978:<PROXY_PASSWORD>' https://example.com/
#
# Self-test:
sudo g3proxy-selftest
# Guide: https://secureanycloud.com/stacks/g3-proxy-1-12-4/Your values differ from the example above. To print the password when you need it, run:
sudo sed -n 's/^PROXY_PASSWORD=//p' /root/g3proxy-credentials.txtConfirm the proxy is running
systemctl is-active g3proxy.service;
sudo ss -Htlnp | awk '{print $1, $4, $6}' | sortactive LISTEN *:3128 users:(("g3proxy",pid=17556,fd=31)) LISTEN 0.0.0.0:22 users:(("sshd",pid=942,fd=3),("systemd",pid=1,fd=161)) LISTEN 127.0.0.53%lo:53 users:(("systemd-resolve",pid=414,fd=15)) LISTEN 127.0.0.54:53 users:(("systemd-resolve",pid=414,fd=17)) LISTEN [::]:22 users:(("sshd",pid=942,fd=4),("systemd",pid=1,fd=162))g3proxy listens on TCP 3128 on every address (IPv4 and IPv6). Its client filter, not the socket, decides who may use it. The only other listeners are SSH and Ubuntu's local DNS stub.
Run the self-test
sudo g3proxy-selftestSELFTEST_OK g3proxy active on :3128; 407 anonymous + wrong password; 403 loopback, metadata, localhost; CONNECT :22 403; internet 200The self-test checks the running service and its start guard. It sends requests without credentials and with a wrong password (both 407), and sends authenticated requests to loopback, the metadata service, localhost and an SSH port (all refused with 403). Finally, it reaches an internet site. If the instance has no route to the internet, the last check reports that instead of 200.
Use the proxy
From the proxy instance itself, send a request through the proxy with your credentials, and then repeat it without them. The credentials go to curl on standard input, so the password never appears on a command line:
U=$(sudo sed -n 's/^PROXY_USERNAME=//p' /root/g3proxy-credentials.txt)
P=$(sudo sed -n 's/^PROXY_PASSWORD=//p' /root/g3proxy-credentials.txt) printf 'proxy-user = "%s:%s"\n' "$U" "$P" | curl -sS -K - -o /dev/null -w 'https://example.com/ through the proxy: %{http_code}\n' --proxy http://127.0.0.1:3128 https://example.com/ curl -s -o /dev/null -w 'the same request without credentials: %{http_connect}\n' --proxy http://127.0.0.1:3128 https://example.com/ || truehttps://example.com/ through the proxy: 200 the same request without credentials: 407On a client instance in the same VPC (after you open TCP 3128 to it in the proxy's security group), point your tools at the proxy's private address. Most tools read the standard proxy variables. Keep the password out of your shell history and out of files that other users can read:
export http_proxy="http://<PROXY_USERNAME>:<PROXY_PASSWORD>@<PROXY_HOST>:3128"
export https_proxy="$http_proxy"
export no_proxy="169.254.169.254,localhost,127.0.0.1" curl -sS -o /dev/null -w '%{http_code}\n' https://example.com/For apt on Ubuntu or Debian clients, put the proxy settings in a root-only file, for example /etc/apt/apt.conf.d/95proxy (mode 0600):
Acquire::http::Proxy "http://<PROXY_USERNAME>:<PROXY_PASSWORD>@<PROXY_HOST>:3128/"; Acquire::https::Proxy "http://<PROXY_USERNAME>:<PROXY_PASSWORD>@<PROXY_HOST>:3128/";Clients outside your VPC (for example over a VPN or a peered VPC) are refused until you add their range to the client list (Step 7).
What the proxy refuses
Even with valid credentials, requests to loopback, metadata and private destinations are refused. The following commands try the metadata service, a loopback name, a public DNS name that resolves to 127.0.0.1, and a private address:
U=$(sudo sed -n 's/^PROXY_USERNAME=//p' /root/g3proxy-credentials.txt)
P=$(sudo sed -n 's/^PROXY_PASSWORD=//p' /root/g3proxy-credentials.txt) for t in http://169.254.169.254/latest/meta-data/ http://localhost/ http://127.0.0.1.nip.io/ http://10.0.0.1/; do printf 'proxy-user = "%s:%s"\n' "$U" "$P" | curl -sS -K - -o /dev/null -w "$t %{http_code}\n" --proxy http://127.0.0.1:3128 "$t" donehttp://169.254.169.254/latest/meta-data/ 403 http://localhost/ 403 http://127.0.0.1.nip.io/ 403 http://10.0.0.1/ 403Each refusal is recorded in the service journal:
sudo journalctl -u g3proxy --no-pager -o cat -n 4forbidden by rule: target dest denied forbidden by rule: target dest denied forbidden by rule: target ip blocked forbidden by rule: target dest deniedtarget dest denied means the destination was refused by its name or literal address before any lookup. target ip blocked means the name's resolved address was refused.
Configuration
The configuration is /etc/g3proxy/main.yml, readable by root and the g3proxy group only. First boot wrote your VPC's ranges into it. To see the client list, run:
sudo sed -n '/ingress_network_filter:/,/dst_port_filter:/p' /etc/g3proxy/main.ymlingress_network_filter: default: forbid permit: - 127.0.0.1/32 - ::1/128 - 172.31.0.0/16
# DESTINATION ports: HTTP (80) and HTTPS (443) only, for plain forwarding and for CONNECT tunnels. dst_port_filter:To see the destination filter that runs after name resolution, run:
sudo sed -n '/^escaper:/,$p' /etc/g3proxy/main.yml | head -12escaper: - name: default type: direct_fixed resolver: default resolve_strategy: IPv4First
# Checked on the RESOLVED destination address. To let clients reach private / VPC destinations through the proxy,
# delete the matching line(s) below (the guide shows how); keep loopback, link-local and fd00:ec2::/32. egress_network_filter: default: permit forbid: - 0.0.0.0/8 - 10.0.0.0/8For common changes, edit the file with sudo nano /etc/g3proxy/main.yml, then run sudo systemctl restart g3proxy and sudo g3proxy-selftest:
- Allow clients from another network (a peered VPC or a VPN range): add its CIDR to the
permitlist underingress_network_filter, and open TCP 3128 in the security group to the same range only. Never add0.0.0.0/0, because the start guard refuses it. - Allow private destinations (for example a package mirror inside your network at 10.20.0.15): delete the matching range (here
10.0.0.0/8) from bothforbidlists, or replace it with narrower ranges that you still want refused. One list is undersubnet_matchindst_host_filter_set, and the other is underegress_network_filter. Keep loopback,169.254.0.0/16,fc00::/7andfe80::/10, because the start guard refuses a configuration without them. - Allow another destination port: add it to the
permitlist underdst_port_filter.
Ports are checked per request, not per method. CONNECT tunnels and plain HTTP forwarding both reach ports 80 and 443 only.
Change the password or add users. Users live in /etc/g3proxy/users.json as SHA-512 crypt hashes, and g3proxy re-reads the file every 60 seconds. The commands below create a hash from a password you type, which is never shown or put on a command line. Add the hash for a new user to the JSON list next to the existing one, with the same proxy_request_filter:
read -rs -p 'New proxy password: ' NEWPW; echo printf '%s\n' "$NEWPW" | openssl passwd -6 -stdin; unset NEWPW
sudo nano /etc/g3proxy/users.jsonIf you replace the first user's password, update /root/g3proxy-credentials.txt too, because g3proxy-selftest reads it.
How the fail-closed start works
systemctl cat g3proxy.service | grep -E '^(User|ConditionPathExists|ExecStartPre|ExecStart|IPAddressDeny)='ConditionPathExists=/var/lib/sac/data/g3proxy-bootstrap-ready User=g3proxy ExecStartPre=+/usr/local/sbin/g3proxy-guard ExecStart=/usr/local/bin/g3proxy -c /etc/g3proxy/main.yml --control-dir /run/g3proxy -s -G default IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128The service does not start until first boot has written the ready marker. g3proxy-guard runs as root before every start. It refuses an unrendered configuration, or one edited to an unsafe state (see Security posture), and it runs g3proxy -t on the configuration. If a start is refused, journalctl -u g3proxy shows the guard's reason.
Logs, backup and upgrades
journalctl -u g3proxy shows the service log, including every refused request (Step 6). g3proxy keeps no other state.
Backup: Back up /etc/g3proxy (the configuration and the hashed users) and /root/g3proxy-credentials.txt, and keep the copy as private as the server. EBS snapshots of the instance work too.
Upgrades: Ubuntu security updates, including the OpenSSL and c-ares libraries that g3proxy uses, install automatically. g3proxy itself is a single binary at /usr/local/bin/g3proxy. To check its version, run:
g3proxy -Vg3proxy 1.12.4SecureAnyCloud ships updated builds as new versions of this listing. To build a newer release yourself, follow the upstream build instructions at github.com/bytedance/g3. Run g3proxy -t -c /etc/g3proxy/main.yml with the new binary, then copy it over /usr/local/bin/g3proxy and run sudo systemctl restart g3proxy. Keep the old binary until the new one runs.
Security recommendations
- Open SSH only to your management addresses, and TCP 3128 only to the clients that use the proxy.
- Give each team or application its own user, so you can rotate one password without touching the others.
- Keep private destinations refused unless a client really needs one, and then allow only that range.
- Keep
169.254.169.254in the clients'no_proxy, so tools on the clients reach their own metadata service directly. - Remember that plain
http://traffic through the proxy is not encrypted between the client and the proxy. Preferhttps://destinations, whose TLS runs end to end through the tunnel.
Support and licensing
G3 Proxy is open-source software by ByteDance Ltd. and its affiliates (the G3 project), distributed under the Apache License 2.0. The licence, NOTICE and LICENSE-FOREIGN are in /usr/share/doc/g3proxy/, and BUILD-PROVENANCE.txt there describes how this build was made from the upstream source.
SecureAnyCloud provides the build, the per-instance credentials, the client and destination filters, the fail-closed start, the metadata block, the self-test and 24/7 support.
SecureAnyCloud is not affiliated with or endorsed by ByteDance or the G3 project.
For help with deployment or configuration, email sales@secureanycloud.com or visit SecureAnyCloud support.
Deploy on AWS
Launch G3 Proxy 1.12.4 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