Application Infrastructure AWS

Envoy 1.40.0 on AWS User Guide

| Product: Envoy 1.40.0 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the Envoy 1.40.0 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg application infrastructure listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

Envoy is the high performance, open source edge and service proxy that started at Lyft and is now a graduated CNCF project. It is the data plane behind Istio and many other service meshes and API gateways. Teams put it in front of their services to terminate TLS, route requests by host and path, balance load across upstream hosts with active health checks, retry and time out requests, and expose detailed statistics for every listener, route and cluster.

The cloudimg image gives you an Envoy that is safe to put on a public address from the first minute:

  • HTTPS from the first boot. Envoy terminates TLS on port 443 with a certificate generated on your instance's first boot (TLS 1.2 or newer, HTTP/2 and HTTP/1.1), and port 80 redirects to HTTPS. No certificate or key is baked into the image.
  • Never an open proxy. The default route sends requests for your instance's own host names to one fixed upstream cluster, demo_backend, which you point at your own service. Requests that name any other host, including absolute URLs and CONNECT tunnels, are refused, so nobody can use your instance to reach other sites or the instance metadata service.
  • The admin interface stays private. Envoy's admin interface listens on 127.0.0.1:9901 only.
  • The real client address. Envoy takes the client address from the TCP connection and overwrites X-Forwarded-For and X-Real-IP for your upstream, so a client cannot spoof its address.
  • Sandboxed and fail closed. Envoy runs as the unprivileged envoy user with only the right to bind ports 80 and 443, under a locked down systemd unit that cannot reach the instance metadata service. It will not start until first boot has generated the certificate, and it validates its configuration before every start.
  • Edge best practices. A global connection limit, per connection buffer limits, header, idle and request timeouts, path normalisation, headers with underscores rejected, and HSTS on HTTPS responses.
  • The official release, verified, and fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: Envoy 1.40.0 (the official release binary at /usr/local/bin/envoy, licence in /usr/share/doc/envoy), the envoy.service and envoy-firstboot.service units, the default configuration in /etc/envoy/envoy.yaml, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.

Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management).
  • A security group that allows TCP 22 (SSH) from your own networks only, and TCP 443 (HTTPS) and TCP 80 (redirects to HTTPS) from the clients Envoy serves. Nothing else needs to be open: the admin interface and the demo backend listen on loopback only.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Envoy 1.40.0 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (Envoy itself is light; size up for heavy TLS traffic), your key pair, and a security group as above.

With the AWS CLI (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=envoy}]'

Step 2: Connect to your instance

Version SSH user
Envoy 1.40.0 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the service

First boot generates the certificate and writes this instance's host names into the configuration, then Envoy starts:

systemctl is-active envoy
sudo test -f /var/lib/cloudimg/envoy-firstboot.done && echo FIRSTBOOT_DONE
envoy --version | grep -o 'version: .*'
active
FIRSTBOOT_DONE
version: 9e5580933739775e8bce696a57a2e1bc99368f23/1.40.0/Clean/RELEASE/BoringSSL

Envoy listens on ports 80 and 443 on every address, and on loopback only for the demo backend (8080) and the admin interface (9901):

sudo ss -lntp | awk 'NR>1 {print $4, $6}' | sed -E 's/pid=[0-9]+,fd=[0-9]+/.../g' | sort -u
0.0.0.0:22 users:(("sshd",...),("systemd",...))
0.0.0.0:443 users:(("envoy",...))
0.0.0.0:80 users:(("envoy",...))
127.0.0.1:8080 users:(("envoy",...))
127.0.0.1:9901 users:(("envoy",...))
127.0.0.53%lo:53 users:(("systemd-resolve",...))
127.0.0.54:53 users:(("systemd-resolve",...))
[::]:22 users:(("sshd",...),("systemd",...))

Step 4: Your instance's URL and certificate

sudo cat /root/envoy-info.txt

The file is readable by root only:

# Envoy 1.40.0 - generated on first boot by envoy-firstboot.service
# The TLS certificate and its private key were generated on this instance's first boot; none was baked
# into the image. Port 80 redirects to https; requests for other host names get 421.

ENVOY_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
ENVOY_HOSTNAME=ec2-<public-ip-dashed>.<region>.compute.amazonaws.com
ENVOY_CERT_SHA256=<sha256-fingerprint>
ENVOY_CONFIG=/etc/envoy/envoy.yaml
ENVOY_ADMIN=http://127.0.0.1:9901/ (loopback only)
ENVOY_DEMO_BACKEND=http://127.0.0.1:8080/ (loopback only)
#
# Put your service behind Envoy:     edit the demo_backend cluster in /etc/envoy/envoy.yaml, then
#                                    sudo envoy --mode validate -c /etc/envoy/envoy.yaml && sudo systemctl restart envoy
# Serve your own DNS name:           sudo envoy-set-hostname <your-dns-name>
# User guide: https://www.cloudimg.co.uk/guides/envoy-1-on-ubuntu-24-04-aws/

The URL uses the instance's public DNS name from EC2 instance metadata. The certificate also names the public and private IP addresses, so https://<public-ip>/ works too. To serve your own DNS name, see Step 8.

Step 5: Send a request through Envoy

Open the ENVOY_URL in your browser (the certificate is self signed, so the browser asks you to confirm it the first time) and you see the demo backend's page, served through Envoy. From the instance itself:

H=$(sudo grep '^ENVOY_HOSTNAME=' /root/envoy-info.txt | cut -d= -f2)
curl -sk --resolve "$H:443:127.0.0.1" "https://$H/" | grep -o '<h1>.*</h1>'
curl -sk -o /dev/null -D - --resolve "$H:443:127.0.0.1" "https://$H/" | tr -d '\r' | grep -vE '^(date|content-length):'
<h1>Envoy is running</h1>
HTTP/2 200 
content-type: text/html; charset=utf-8
server: envoy
strict-transport-security: max-age=31536000
x-content-type-options: nosniff

Port 80 redirects to HTTPS, a host name the instance does not serve gets 421 Misdirected Request, and requests that try to use Envoy as a forward proxy (an absolute URL or a CONNECT tunnel to another host, here the instance metadata address) are refused:

H=$(sudo grep '^ENVOY_HOSTNAME=' /root/envoy-info.txt | cut -d= -f2)
curl -s -o /dev/null -w 'http:// on port 80        -> %{http_code} %{redirect_url}\n' --resolve "$H:80:127.0.0.1" "http://$H/"
curl -sk -o /dev/null -w 'unknown host name         -> %{http_code}\n' --resolve "other.example:443:127.0.0.1" "https://other.example/"
curl -s -o /dev/null -w 'absolute URL for another host -> %{http_code}\n' --proxy http://127.0.0.1:80 http://169.254.169.254/latest/meta-data/
curl -s -o /dev/null -w 'CONNECT tunnel            -> %{http_connect}\n' -p --proxy http://127.0.0.1:80 http://169.254.169.254/ || true
http:// on port 80        -> 301 https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
unknown host name         -> 421
absolute URL for another host -> 421
CONNECT tunnel            -> 404

Step 6: The admin interface

The admin interface answers on loopback only. It reports readiness, the running version, the health of every upstream host and Envoy's statistics:

curl -s http://127.0.0.1:9901/ready; echo
curl -s http://127.0.0.1:9901/server_info | jq -r '.version, .state'
curl -s http://127.0.0.1:9901/clusters | grep -E '^demo_backend::127.0.0.1:8080::health_flags'
curl -s 'http://127.0.0.1:9901/stats?filter=^(cluster\.demo_backend\.health_check\.success|listener\.0\.0\.0\.0_443\.ssl\.handshake)$'
LIVE

9e5580933739775e8bce696a57a2e1bc99368f23/1.40.0/Clean/RELEASE/BoringSSL
LIVE
demo_backend::127.0.0.1:8080::health_flags::healthy
cluster.demo_backend.health_check.success: 13
listener.0.0.0.0_443.ssl.handshake: 6

To browse the admin page from your own computer, forward it over SSH and open http://127.0.0.1:9901/ locally. The admin interface can change logging levels and shut Envoy down, so never open port 9901 in a security group:

ssh -i <your-key.pem> -L 9901:127.0.0.1:9901 ubuntu@<public-ip>

Step 7: Put your own service behind Envoy

The default route sends every request for your host names to the demo_backend cluster. Point that cluster at your own service (an application on this instance, or a private IP address in your VPC), then validate and restart. Open the configuration:

sudo nano /etc/envoy/envoy.yaml

Find the cluster at the end of the file and replace the endpoint address and port with your service, for example an application on port 3000 of a private host. Keep the health check path pointing at a URL your service answers with 200, or remove the health_checks block:

  clusters:
  - name: demo_backend
    type: STATIC
    connect_timeout: 2s
    lb_policy: ROUND_ROBIN
    per_connection_buffer_limit_bytes: 32768
    load_assignment:
      cluster_name: demo_backend
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address: { address: 10.0.1.25, port_value: 3000 }

Add more - endpoint: entries to balance across several hosts, use type: STRICT_DNS with a host name instead of an IP address, or add routes and clusters for more services; the Envoy documentation covers every option. Check the file before you restart, so a typing mistake never takes the proxy down:

sudo envoy --mode validate -c /etc/envoy/envoy.yaml --log-level error
configuration '/etc/envoy/envoy.yaml' OK
sudo systemctl restart envoy

The demo backend listener (demo_backend on 127.0.0.1:8080) can stay or go; once your own service is behind Envoy you can delete that listener from the file. Whatever you route to, Envoy cannot reach the instance metadata service (Step 9).

Step 8: Your own DNS name and certificate

Point a DNS record at the instance, then set the name Envoy serves. This mints a new self signed certificate for the name, rewrites the two domains: lists marked # cloudimg-hostnames in /etc/envoy/envoy.yaml, restarts Envoy and keeps the name across reboots:

sudo envoy-set-hostname <your-domain>

To use a certificate from your own certificate authority instead, copy it over the generated pair and restart Envoy (running envoy-set-hostname again replaces it with a self signed one):

sudo install -m 0644 <your-domain>.crt /etc/ssl/envoy/envoy.crt
sudo install -m 0640 -g envoy <your-domain>.key /etc/ssl/envoy/envoy.key
sudo envoy --mode validate -c /etc/envoy/envoy.yaml && sudo systemctl restart envoy

Step 9: Check the security posture

The certificate was generated on this instance and names its own addresses; its private key is readable only by root and the envoy group:

H=$(sudo grep '^ENVOY_HOSTNAME=' /root/envoy-info.txt | cut -d= -f2)
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
sudo stat -c '%a %U:%G %n' /etc/ssl/envoy/envoy.key /etc/ssl/envoy/envoy.crt /etc/envoy/envoy.yaml
subject=CN = ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, O = cloudimg
X509v3 Subject Alternative Name: 
    DNS:ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, IP Address:<public-ip>, IP Address:<private-ip>, IP Address:127.0.0.1, DNS:localhost
640 root:envoy /etc/ssl/envoy/envoy.key
644 root:root /etc/ssl/envoy/envoy.crt
640 root:envoy /etc/envoy/envoy.yaml

Envoy takes the client address from the connection. A request that claims to come from somewhere else in X-Forwarded-For is logged with its real address, and the upstream receives only that address (Envoy writes its access logs every few seconds, hence the pause):

H=$(sudo grep '^ENVOY_HOSTNAME=' /root/envoy-info.txt | cut -d= -f2)
P=$(hostname -I | awk '{print $1}')
curl -sk -o /dev/null --resolve "$H:443:$P" -H 'X-Forwarded-For: 203.0.113.9' "https://$H/spoof-test"
sleep 12
sudo grep -h 'spoof-test' /var/log/envoy/access.log | tail -1
sudo grep -h 'spoof-test' /var/log/envoy/demo-backend.log | tail -1
<private-ip> [2026-10-19T21:01:58.411Z] "GET /spoof-test HTTP/2" 200 - 0 391 0ms "ec2-<public-ip-dashed>.<region>.compute.amazonaws.com" "curl/8.5.0" upstream=127.0.0.1:8080
[2026-10-19T21:01:58.411Z] "GET /spoof-test" 200 x-forwarded-for=<private-ip> x-real-ip=<private-ip> x-forwarded-proto=https

Envoy runs as the envoy user with only the right to bind low ports, cannot reach the instance metadata service, and does not start before first boot has finished or with a configuration that fails validation:

systemctl show envoy -p User -p AmbientCapabilities -p NoNewPrivileges -p ProtectSystem -p ProtectHome -p IPAddressDeny
systemctl cat envoy | grep -E '^ExecStartPre='
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128
AmbientCapabilities=cap_net_bind_service
User=envoy
ProtectHome=yes
ProtectSystem=strict
NoNewPrivileges=yes
ExecStartPre=/usr/bin/test -f /var/lib/cloudimg/envoy-firstboot.done
ExecStartPre=/usr/bin/test -s /etc/ssl/envoy/envoy.key
ExecStartPre=/usr/local/bin/envoy --mode validate -c /etc/envoy/envoy.yaml --log-level warn

Step 10: Logs

Envoy writes one access log line per request to /var/log/envoy/access.log (client address first), the demo backend writes its own to /var/log/envoy/demo-backend.log, and both are rotated daily. Envoy's own messages go to the journal:

sudo tail -n 3 /var/log/envoy/access.log | cut -c1-160
sudo journalctl -u envoy --no-pager -o cat | grep -E 'starting main dispatch loop|all clusters initialized' | tail -2
127.0.0.1 [<time>] "GET /latest/meta-data/ HTTP/1.1" 421 - 0 59 0ms "169.254.169.254" "curl/8.5.0" upstream=-
127.0.0.1 [<time>] "CONNECT - HTTP/1.1" 404 NR 0 0 0ms "169.254.169.254" "curl/8.5.0" upstream=-
<private-ip> [<time>] "GET /spoof-test HTTP/2" 200 - 0 391 1ms "ec2-<public-ip-dashed>.<region>.compute.amazonaws.com" "curl/8.5.0" upstream=127.0.0.1:8080
[<time>][1757][info][upstream] [source/common/upstream/cluster_manager_impl.cc:251] cm init: all clusters initialized
[<time>][1757][info][main] [source/server/server.cc:1078] all clusters initialized. initializing init manager

Step 11: Server components

Component Path
Envoy binary /usr/local/bin/envoy
Configuration /etc/envoy/envoy.yaml
Certificate and key /etc/ssl/envoy/
Logs /var/log/envoy/ (rotated by /etc/logrotate.d/envoy)
Service units envoy.service, envoy-firstboot.service
Metadata block /etc/systemd/system/envoy.service.d/50-cloudimg-imds-deny.conf
First boot script /usr/local/sbin/envoy-firstboot.sh
Host name helper /usr/local/sbin/envoy-set-hostname
Licence and source note /usr/share/doc/envoy/
Instance details /root/envoy-info.txt (mode 0600)
ls -l /usr/share/doc/envoy | awk 'NR>1{print $1,$5,$9}'
sha256sum /usr/local/bin/envoy
-rw-r--r-- 11358 LICENSE
-rw-r--r-- 440 README.cloudimg.txt
8aa137a7d00994ce43f613556f5a120709229f63bedb86eedde5ccbc402c2b80  /usr/local/bin/envoy

The checksum is the one published for envoy-1.40.0-linux-x86_64 in the signed checksums.txt.asc of the v1.40.0 release.

Step 12: Updating Envoy

Ubuntu's security updates install automatically. Envoy is a single binary, so you update it by replacing the file with a newer official release after checking its published checksum, validating your configuration with the new binary, and restarting:

V=<version>
curl -fsSL -o /tmp/envoy "https://github.com/envoyproxy/envoy/releases/download/v$V/envoy-$V-linux-x86_64"
sha256sum /tmp/envoy
/tmp/envoy --mode validate -c /etc/envoy/envoy.yaml
sudo install -m 0755 /tmp/envoy /usr/local/bin/envoy && sudo systemctl restart envoy

Support

cloudimg provides 24/7 technical support for this image by email and live chat: deployment, HTTPS and certificates, routing your services, upgrades and troubleshooting. Contact support@cloudimg.co.uk.

Envoy is a trademark of The Linux Foundation. cloudimg is not affiliated with or endorsed by The Linux Foundation or the Envoy project.