Tinyproxy 1.11.1 on AWS User Guide
Overview
This guide covers launching and verifying Tinyproxy 1.11.1 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.
Tinyproxy is a small, fast HTTP and HTTPS forward proxy daemon. Clients send their outbound web requests to it and it fetches them on their behalf, so you can give instances in private subnets controlled web access, put one egress address in front of a group of hosts, or reach the web from a locked-down network through a single audited point. It has no database and no web console: it is configured with one text file.
The image installs Tinyproxy 1.11.1 from the Ubuntu 24.04 LTS archive, which the Ubuntu security team patches. Unattended security upgrades stay on for your instance.
What is on the image:
- Tinyproxy 1.11.1 (
tinyproxy.service) listening on TCP 8888, running as the unprivilegedtinyproxyuser - HTTP Basic proxy authentication with a password generated on your instance's first boot
- A not an open proxy policy: only the instance itself and private (RFC 1918 / ULA) source addresses may connect
CONNECTtunnelling limited to the TLS ports 443 and 563, and theViaheader turned off- No metadata access for the proxy:
tinyproxy.servicecannot reach the instance metadata service - A self-test,
/usr/local/sbin/tinyproxy-roundtrip.sh
Default user: ubuntu · Architecture: x86_64 · Root volume: 8 GiB gp3
Security posture
An open forward proxy on the internet is quickly found and abused for spam, credential stuffing and hiding attack traffic. This image is built so that cannot happen by default:
- Authentication is mandatory. Every request must carry the user
cloudimgand the password created on your instance's first boot. A request with no credentials gets HTTP 407 and a wrong password gets HTTP 401. No password ships in the image, so no two instances share one. - Private sources only. The
Allowrules admit127.0.0.1,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16andfc00::/7. Any other source address is refused with HTTP 403 before authentication, even with the right password. There is noAllow 0.0.0.0/0. - The listing security group opens SSH only. Port 8888 stays closed until you open it to your own VPC or client networks, or you reach it through an SSH tunnel. Clients use the instance's private IP.
- No SSRF to instance credentials. A proxy fetches whatever URL a client names, so
tinyproxy.serviceis blocked from the instance metadata service (169.254.169.254, fd00:ec2::254). A request forhttp://169.254.169.254/through the proxy never gets an answer, even with valid credentials. Amazon Time Sync (169.254.169.123) still works. - IMDSv2 required. The AMI is registered with
ImdsSupport v2.0, so instances launched from it refuse token-less (IMDSv1) metadata requests. - Fails closed. Tinyproxy starts only after first boot has written the password and a ready marker, and a start-up check refuses to run it without a
BasicAuthline or with an allow-all rule. If first boot cannot finish, the proxy stays stopped and first boot tries again on the next boot. - The password file is protected.
/etc/tinyproxy/tinyproxy.confholds the password and is readable only by root and thetinyproxygroup;/root/tinyproxy-credentials.txtis root only. The password is never written to a log. - Per-instance identity. SSH host keys and the machine ID are generated fresh on every instance.
Prerequisites
- An AWS account and permission to launch EC2 instances
- An EC2 key pair in the Region you launch in
- A VPC subnet for the proxy, and the CIDR ranges of the clients that will use it
Recommended instance type: m5.large. A small proxy for a handful of clients runs comfortably on t3.small or larger.
Connecting to your instance
| Version | Operating system | SSH user |
|---|---|---|
| Tinyproxy 1.11.1 on Ubuntu 24.04 | Ubuntu 24.04 LTS | ubuntu |
Step 1: Launch the instance
Option A: AWS Marketplace console
- 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.
- Once the subscription is active, choose Launch your software.
- Choose One-click launch from AWS Marketplace (or Launch from EC2 Console for the full launch wizard). In Version, choose Tinyproxy 1.11.1 on Ubuntu 24.04, then choose your Region.
- Pick an instance type (
m5.largerecommended; the software charge is $0.04 per vCPU per hour) and your key pair. - Security group: the listing's default group allows SSH (22) only. Restrict it to your own IP address. You will open the proxy port to your clients in Step 5.
- Choose your VPC and subnet, 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 VPC 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 and CIDR ranges with your own:
SG=$(aws ec2 create-security-group --group-name tinyproxy \
--description "Tinyproxy forward 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 8888 --cidr 10.0.0.0/16
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 \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=tinyproxy}]'
You do not need --metadata-options: the AMI already makes IMDSv2 required.
Step 2: Connect and read the credentials
Connect as ubuntu with the key pair you chose:
ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip>
First boot finishes within a minute of launch. It generates this instance's proxy password, writes it into the Tinyproxy configuration, records the instance's private IP, starts the proxy and runs the self-test. The details are in /root/tinyproxy-credentials.txt, readable only by root. This command prints everything except the password line:
sudo grep -v '^proxy\.pass=' /root/tinyproxy-credentials.txt
# Tinyproxy 1.11.1 on Ubuntu 24.04 (cloudimg AWS Marketplace image) - THIS instance.
# The proxy password below was generated on this instance's first boot and exists nowhere else.
proxy.user=cloudimg
proxy.host=172.31.91.11
proxy.port=8888
proxy.endpoint=172.31.91.11:8888
proxy.public_ip=3.83.230.145
#
# NOT AN OPEN PROXY: only localhost and private (RFC1918/ULA) source addresses may connect
# (Allow lines in /etc/tinyproxy/tinyproxy.conf), and every request must carry the user and
# password above (HTTP 407 without credentials, 401 with wrong ones). The proxy cannot reach the instance metadata service.
#
# NETWORK ACCESS: the listing security group opens SSH only. To serve clients in your VPC,
# add an inbound rule for TCP 8888 ONLY from your VPC CIDR or client security group - never
# 0.0.0.0/0 - and point clients at the PRIVATE IP above. Or use an SSH tunnel:
# ssh -i <key.pem> -N -L 8888:127.0.0.1:8888 ubuntu@<public-ip>
#
# Test from a client:
# curl -x http://cloudimg:<password>@172.31.91.11:8888 https://example.com/ -sS -o /dev/null -w '%{http_code}\n'
# Rotate the password: edit the 'BasicAuth cloudimg <password>' line in
# /etc/tinyproxy/tinyproxy.conf, update this file, then: sudo systemctl restart tinyproxy
# Self-test: sudo /usr/local/sbin/tinyproxy-roundtrip.sh
# User guide: https://www.cloudimg.co.uk/guides/tinyproxy-1-11-1-on-ubuntu-24-04-aws/
Print the password itself only when you need it, with sudo sed -n 's/^proxy\.pass=//p' /root/tinyproxy-credentials.txt. The file also contains a proxy.pass= line with the 40 or so character password.
Step 3: Confirm the proxy is running
tinyproxy.service is active, first boot has completed, and Tinyproxy is the process listening on port 8888 on every interface (IPv4 and IPv6). The security group and the Allow rules decide who can actually reach it.
systemctl is-active tinyproxy.service tinyproxy-firstboot.service
tinyproxy -v
sudo ss -Htlnp 'sport = :8888'
active
active
tinyproxy 1.11.1
LISTEN 0 1024 0.0.0.0:8888 0.0.0.0:* users:(("tinyproxy",pid=9022,fd=3))
LISTEN 0 1024 [::]:8888 [::]:* users:(("tinyproxy",pid=9022,fd=4))
Step 4: Run the self-test
The shipped self-test checks the whole security posture in one go: an anonymous request is refused with 407, a wrong password with 401, the instance's password fetches a page from a local test server with 200, a request for the instance metadata service through the proxy gets no answer while the same request straight from the instance shell succeeds, and the only non-loopback listening ports are 22 and 8888. It takes about 20 seconds because it waits out the blocked metadata requests.
sudo /usr/local/sbin/tinyproxy-roundtrip.sh
round-trip OK: anonymous 407, wrong password 401, per-instance password 200 (body proxied); instance metadata through the proxy never answers (connect dropped by the tinyproxy.service IMDS deny) while the host shell reaches it (200); listening tcp: 22 8888
Step 5: Open the proxy to your clients
Add an inbound rule for TCP 8888 to the instance's security group, only from the networks that need the proxy (your VPC CIDR, a subnet, or a client security group). Never open 8888 to 0.0.0.0/0. Even if you did, the Allow rules would refuse every public source, but the rule keeps the port private at the network edge as well:
aws ec2 authorize-security-group-ingress --group-id <sg-id> --protocol tcp --port 8888 --cidr <vpc-cidr>
Then point clients at the proxy's private IP (proxy.host in the credentials file). On a Linux client, set the standard proxy variables with the user and password from Step 2:
export http_proxy="http://cloudimg:<PROXY_PASS>@<PROXY_HOST>:8888"
export https_proxy="$http_proxy"
export no_proxy="localhost,127.0.0.1,169.254.169.254"
curl -sS -o /dev/null -w '%{http_code}\n' https://example.com/
Keep 169.254.169.254 in no_proxy on the clients, so each client reads its own instance metadata directly.
Using the proxy from your workstation through SSH
To use the proxy from a machine outside the VPC without opening port 8888 at all, forward it over SSH and point your browser or tools at 127.0.0.1:8888 (requests then reach Tinyproxy from 127.0.0.1, which the Allow rules admit; the password is still required):
ssh -i ~/.ssh/my-key-pair.pem -N -L 8888:127.0.0.1:8888 ubuntu@<public-ip>
Step 6: Make requests through the proxy
On the instance, these requests show each layer at work: no credentials (407), a wrong password (401), and the instance's password over plain HTTP and HTTPS (CONNECT to port 443). The password is passed to curl on standard input (-K -) so it never appears in a process listing or your shell history.
PROXY_PASS="$(sudo sed -n 's/^proxy\.pass=//p' /root/tinyproxy-credentials.txt)"
curl -s -o /dev/null -w 'no credentials: HTTP %{http_code}\n' -x http://127.0.0.1:8888 http://example.com/
printf 'proxy-user = "cloudimg:wrong-password"\n' | curl -K - -s -o /dev/null -w 'wrong password: HTTP %{http_code}\n' -x http://127.0.0.1:8888 http://example.com/
printf 'proxy-user = "cloudimg:%s"\n' "$PROXY_PASS" | curl -K - -s -o /dev/null -w 'with password: HTTP %{http_code} (http)\n' -x http://127.0.0.1:8888 http://example.com/
printf 'proxy-user = "cloudimg:%s"\n' "$PROXY_PASS" | curl -K - -s -o /dev/null -w 'with password: HTTP %{http_code} (https via CONNECT)\n' -x http://127.0.0.1:8888 https://example.com/
no credentials: HTTP 407
wrong password: HTTP 401
with password: HTTP 200 (http)
with password: HTTP 200 (https via CONNECT)
Step 7: Confirm it is not an open proxy
The access rules, the CONNECT ports and the file permissions:
sudo grep -E '^[[:space:]]*(Port|Allow|ConnectPort|DisableViaHeader)' /etc/tinyproxy/tinyproxy.conf
sudo grep -c '^BasicAuth ' /etc/tinyproxy/tinyproxy.conf
sudo stat -c '%a %U:%G %n' /etc/tinyproxy/tinyproxy.conf /root/tinyproxy-credentials.txt
Port 8888
Allow 127.0.0.1
Allow ::1
Allow 10.0.0.0/8
Allow 172.16.0.0/12
Allow 192.168.0.0/16
Allow fc00::/7
DisableViaHeader Yes
ConnectPort 443
ConnectPort 563
1
640 root:tinyproxy /etc/tinyproxy/tinyproxy.conf
600 root:root /root/tinyproxy-credentials.txt
To see the refusal of a public source for yourself, this block adds a documentation-range address (192.0.2.88, TEST-NET-1) to the loopback interface for a moment, sends a request from it with the correct password, and removes the address again. Tinyproxy answers 403 because the source is not private; the same request from 127.0.0.1 succeeds:
PROXY_PASS="$(sudo sed -n 's/^proxy\.pass=//p' /root/tinyproxy-credentials.txt)"
sudo ip addr add 192.0.2.88/32 dev lo
printf 'proxy-user = "cloudimg:%s"\n' "$PROXY_PASS" | curl -K - -s -o /dev/null -w 'from 192.0.2.88 (public): HTTP %{http_code}\n' --interface 192.0.2.88 -x http://192.0.2.88:8888 http://example.com/
printf 'proxy-user = "cloudimg:%s"\n' "$PROXY_PASS" | curl -K - -s -o /dev/null -w 'from 127.0.0.1 (local): HTTP %{http_code}\n' --interface 127.0.0.1 -x http://127.0.0.1:8888 http://example.com/
sudo ip addr del 192.0.2.88/32 dev lo
from 192.0.2.88 (public): HTTP 403
from 127.0.0.1 (local): HTTP 200
Step 8: Metadata access is blocked for the proxy
tinyproxy.service carries an IPAddressDeny rule for the metadata addresses, so a client cannot use the proxy to read the instance's IAM role credentials. A request for the metadata service through the proxy, with the correct password, times out with no answer (000), while the same request made directly from the instance shell is answered:
systemctl show -p IPAddressDeny tinyproxy.service
PROXY_PASS="$(sudo sed -n 's/^proxy\.pass=//p' /root/tinyproxy-credentials.txt)"
printf 'proxy-user = "cloudimg:%s"\n' "$PROXY_PASS" | curl -K - -s -m 5 -o /dev/null -w 'metadata through the proxy: HTTP %{http_code}\n' -x http://127.0.0.1:8888 http://169.254.169.254/latest/meta-data/ || true
curl -s -m 5 -o /dev/null -w 'metadata from the shell: HTTP %{http_code} (IMDSv2 token request)\n' -X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 60' http://169.254.169.254/latest/api/token
IPAddressDeny=fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32
metadata through the proxy: HTTP 000
metadata from the shell: HTTP 200 (IMDSv2 token request)
Step 9: How the fail-closed start works
Three systemd drop-ins sit on top of the packaged unit: the gate (start only once first boot has written /var/lib/cloudimg/tinyproxy-ready, after the start-up check passes), the metadata block, and process sandboxing:
ls /etc/systemd/system/tinyproxy.service.d/
cat /etc/systemd/system/tinyproxy.service.d/10-cloudimg-gate.conf
systemctl show -p ExecStartPre tinyproxy.service | grep -o 'path=[^ ;]*'
10-cloudimg-gate.conf
50-cloudimg-imds-deny.conf
60-cloudimg-hardening.conf
[Unit]
# No ordering against tinyproxy-firstboot.service: first boot itself restarts tinyproxy once the
# marker exists (an After= here would deadlock that restart). Before the marker exists the unit
# is skipped by the condition, never started.
ConditionPathExists=/var/lib/cloudimg/tinyproxy-ready
[Service]
ExecStartPre=/usr/local/sbin/tinyproxy-preflight.sh
path=/usr/local/sbin/tinyproxy-preflight.sh
The start-up check, /usr/local/sbin/tinyproxy-preflight.sh, refuses to start Tinyproxy if the configuration has no BasicAuth line with a password of at least 20 characters, contains an Allow rule for the whole internet, or has become world-readable. If you deliberately want a different policy, edit that script rather than removing the check.
Step 10: Allow another of your own networks (optional)
Clients on a private range are already admitted. If yours use a non-RFC 1918 range (for example a carrier-grade NAT range on a VPN), add an Allow line for it next to the others in /etc/tinyproxy/tinyproxy.conf, never 0.0.0.0/0:
Allow 100.64.0.0/10
Then restart Tinyproxy and run the self-test again:
sudo systemctl restart tinyproxy
sudo /usr/local/sbin/tinyproxy-roundtrip.sh | cut -c1-60
round-trip OK: anonymous 407, wrong password 401, per-instan
Step 11: Rotate the password (optional)
The password is the BasicAuth cloudimg <password> line in /etc/tinyproxy/tinyproxy.conf. To rotate it, this block generates a new value inside a root shell, replaces that line and the credentials file entry, and restarts Tinyproxy. The new value never appears on a sudo command line, so it is not written to /var/log/auth.log:
sudo bash -c 'NEW="$(openssl rand -base64 33 | tr -dc A-Za-z0-9)"
sed -i "s/^BasicAuth cloudimg .*/BasicAuth cloudimg ${NEW}/" /etc/tinyproxy/tinyproxy.conf
sed -i "s/^proxy\.pass=.*/proxy.pass=${NEW}/" /root/tinyproxy-credentials.txt
systemctl restart tinyproxy'
sudo /usr/local/sbin/tinyproxy-roundtrip.sh | cut -c1-14
Update the clients with the new password. You can add more users with extra BasicAuth <user> <password> lines.
Step 12: Restrict destinations (optional)
Tinyproxy can also limit where clients may go. To allow only a list of domains, create /etc/tinyproxy/filter with one pattern per line (for example ^example\.com$ and \.amazonaws\.com$) and add these lines to the configuration, then restart Tinyproxy:
Filter "/etc/tinyproxy/filter"
FilterType ere
FilterDefaultDeny Yes
Step 13: Security recommendations
- Keep TCP 8888 open only to the networks that need the proxy, and TCP 22 only to your management addresses.
- Keep the
BasicAuthline. The start-up check enforces it; it is what makes a mis-set security group harmless. - Use a destination filter (Step 12) if the proxy is the only egress for a private subnet.
- Watch
/var/log/tinyproxy/tinyproxy.log(request lines, never passwords) and ship it to your log platform. - Leave unattended security upgrades on, and reboot after kernel updates.
Support and licensing
Tinyproxy is free software maintained by the Tinyproxy project and its contributors, distributed under the GNU General Public License, version 2 or later (GPL-2.0-or-later). This image ships the unmodified Ubuntu 24.04 Tinyproxy package. cloudimg provides the packaging, the not an open proxy defaults, the per-instance authentication, the metadata block, the fail-closed start, the self-test and 24/7 support.
cloudimg is not affiliated with or endorsed by the Tinyproxy project. Tinyproxy is used here only to identify the software.
For help with deployment or configuration, email support@cloudimg.co.uk or visit cloudimg support.