Gp
Networking AWS

G3 Proxy 1.12.4 on AWS User Guide

| Product: G3 Proxy 1.12.4 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and verifying G3 Proxy 1.12.4 on Ubuntu 24.04 from cloudimg on Amazon EC2. It is one version of the cloudimg 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.

g3proxy 1.12.4 on this image is compiled by cloudimg 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 upstream's own Cargo.toml allows, which 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). /usr/share/doc/g3proxy/BUILD-PROVENANCE.txt lists every pinned input and the checksum of the installed binary.

What is on the image:

  • 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; g3proxy stores only a SHA-512 crypt hash of the password
  • Helper: g3proxy-selftest (an end-to-end check that 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: you open TCP 3128 to the clients that should use the proxy (Step 1).
  • Only web destinations, never internal ones. Requests may go to ports 80 and 443 only, for plain forwarding and for CONNECT alike; 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, an encoded address such as 0x7f000001) 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.service carries an IPAddressDeny rule 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. g3proxy runs 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.service starts 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. cloudimg 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. cargo audit on the compiled binary (whose dependency list is embedded with cargo-auditable; the result ships as /usr/share/doc/g3proxy/cargo-audit-bin.json) reports three advisories with no fix in the versions upstream allows, none reachable in this configuration: RUSTSEC-2025-0143 (capnp, used only on the local control socket), RUSTSEC-2026-0119 (hickory-proto, whose resolver this configuration does not use: it resolves with c-ares) and RUSTSEC-2026-0118 (hickory-proto DNSSEC validation, 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. cloudimg tracks both and publishes new versions in this listing when updates appear.

Prerequisites

  1. An AWS account and permission to launch EC2 instances
  2. An EC2 key pair in the Region you launch in
  3. A VPC whose instances should use the proxy (they reach it on the proxy's private address)
  4. 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; size for the traffic your clients send through it.

Connecting to your instance

Version Operating system SSH user
G3 Proxy 1.12.4 on Ubuntu 24.04 Ubuntu 24.04 LTS ubuntu

Step 1: Launch the instance

Option A: AWS Marketplace console

  1. Open the cloudimg 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.
  2. Once the subscription is active, choose Launch your software.
  3. Choose Launch from EC2 Console. In Version, choose G3 Proxy 1.12.4 on Ubuntu 24.04, then choose your Region.
  4. Pick an instance type (m5.large recommended; the software charge is $0.04 per vCPU per hour) and your key pair.
  5. 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.
  6. Choose a subnet from which the instance can reach the internet, leave the metadata defaults (the image already requires IMDSv2), then choose Launch.

Option B: AWS CLI

Run this on your own workstation. It creates a security group that allows SSH from your management address and the proxy port from your clients' security group only, then launches the instance. Replace the AMI ID with the one shown for your Region on the Marketplace launch page, and 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: the AMI already makes IMDSv2 required.

Step 2: 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 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://www.cloudimg.co.uk/guides/g3-proxy-1-12-4-on-ubuntu-24-04-aws/

Your values differ. To print the password when you need it:

sudo sed -n 's/^PROXY_PASSWORD=//p' /root/g3proxy-credentials.txt

Step 3: Confirm the proxy is running

systemctl is-active g3proxy.service; sudo ss -Htlnp | awk '{print $1, $4, $6}' | sort
active
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.

Step 4: Run the self-test

sudo g3proxy-selftest
SELFTEST_OK g3proxy active on :3128; 407 anonymous + wrong password; 403 loopback, metadata, localhost; CONNECT :22 403; internet 200

The self-test checks the running service and its start guard, sends requests without credentials and with a wrong password (both 407), sends authenticated requests to loopback, the metadata service, localhost and an SSH port (all refused with 403), and finally reaches an internet site. If the instance has no route to the internet, the last part says so instead of 200.

Step 5: Use the proxy

From the proxy instance itself, this request goes through the proxy with your credentials, then repeats 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/ || true
https://example.com/ through the proxy: 200
the same request without credentials: 407

On a client instance in the same VPC (after you opened 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 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 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).

Step 6: What the proxy refuses

With valid credentials, requests to internal destinations are still refused. These are 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"
done
http://169.254.169.254/latest/meta-data/ 403
http://localhost/ 403
http://127.0.0.1.nip.io/ 403
http://10.0.0.1/ 403

Each refusal is recorded in the service journal:

sudo journalctl -u g3proxy --no-pager -o cat -n 4
forbidden by rule: target dest denied
forbidden by rule: target dest denied
forbidden by rule: target ip blocked
forbidden by rule: target dest denied

target dest denied is a destination refused by its name or literal address before any lookup; target ip blocked is a name whose resolved address is refused.

Step 7: Configuration

The configuration is /etc/g3proxy/main.yml (root and the g3proxy group only). First boot wrote your VPC's ranges into it. The client list:

sudo sed -n '/ingress_network_filter:/,/dst_port_filter:/p' /etc/g3proxy/main.yml
    ingress_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:

The destination filter that runs after name resolution:

sudo sed -n '/^escaper:/,$p' /etc/g3proxy/main.yml | head -12
escaper:
  - 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/8

Common changes (edit the file with sudo nano /etc/g3proxy/main.yml, then sudo systemctl restart g3proxy and sudo g3proxy-selftest):

  • Allow clients from another network (a peered VPC, a VPN range): add its CIDR under ingress_network_filter → permit, and open TCP 3128 in the security group to the same range only. Never add 0.0.0.0/0; the start guard refuses it.
  • Allow private destinations (for example an internal package mirror at 10.20.0.15): delete the matching range (here 10.0.0.0/8) from both forbid lists, the one under dst_host_filter_set → subnet_match and the one under egress_network_filter, or replace it with narrower ranges you still want refused. Keep loopback, 169.254.0.0/16, fc00::/7 and fe80::/10; the start guard refuses a configuration without them.
  • Allow another destination port: add it under dst_port_filter → permit.

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. This creates a hash from a password you type (it is never shown or put on a command line), for a user you then add 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.json

Update /root/g3proxy-credentials.txt too if you replace the first user's password, because g3proxy-selftest reads it.

Step 8: How the fail-closed start works

systemctl cat g3proxy.service | grep -E '^(User|ConditionPathExists|ExecStartPre|ExecStart|IPAddressDeny)='
ConditionPathExists=/var/lib/cloudimg/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/128

The service does not start until first boot has written the ready marker, and g3proxy-guard runs as root before every start: it refuses an unrendered or edited-to-unsafe configuration (see Security posture) and runs g3proxy -t on it. If a start is refused, journalctl -u g3proxy shows the guard's reason.

Step 9: 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 g3proxy uses, install automatically. g3proxy itself is a single binary at /usr/local/bin/g3proxy:

g3proxy -V
g3proxy 1.12.4

cloudimg 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).

Step 10: Security recommendations

  • Keep SSH open 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.254 in 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; prefer https:// 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. cloudimg 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.

cloudimg is not affiliated with or endorsed by ByteDance or the G3 project.

For help with deployment or configuration, email support@cloudimg.co.uk or visit cloudimg support.