Deployment guide

Envoy 1.40.0

Overview

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.

This guide covers launching and using the Envoy 1.40.0 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. The image gives you an Envoy that is safe to put on a public address from the first minute. It is one version of the SecureAnyCloud application infrastructure listing on AWS Marketplace, and you choose it as the software version when you subscribe and launch.

What you get:

  • 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.
  • A private admin interface. 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.
  • A sandboxed, fail-closed service. 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 that 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 that Envoy serves. Nothing else needs to be open, because the admin interface and the demo backend listen on loopback only.
1

Launch the instance

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

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=envoy}]'
2

Connect to your instance

Connect to Envoy 1.40.0 on Ubuntu 24.04 as the SSH user ubuntu, with the key pair you chose at launch:

ssh -i <your-key.pem> ubuntu@<public-ip>
3

Check the service

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

systemctl is-active envoy
sudo test -f /var/lib/sac/data/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",...))
4

Your instance's URL and certificate

sudo cat /root/envoy-info.txt

Only root can read this file:

# 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://secureanycloud.com/stacks/envoy-1-40-0/

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.

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. You then see the demo backend's page, served through Envoy. To test 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
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>
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), and then validate the configuration and restart Envoy. 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 that 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. Validate the file before you restart, so that 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).

8

Your own DNS name and certificate

Point a DNS record at the instance, and then set the name that Envoy serves. This creates a new self-signed certificate for the name, rewrites the two domains: lists marked # sac-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
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 = sac 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/sac/data/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
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
11

Server components

Components and paths:

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-sac-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.sac.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.

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

SecureAnyCloud provides 24/7 technical support for this image by email and live chat: deployment, HTTPS and certificates, routing your services, upgrades and troubleshooting. Contact sales@secureanycloud.com.

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

Deploy on AWS

Launch Envoy 1.40.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

Deploy on AWS