Gh
Developer Tools AWS

go-httpbin 2.26.0 on AWS User Guide

| Product: go-httpbin 2.26.0 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the go-httpbin 2.26.0 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg developer and API tools listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

go-httpbin is a Go implementation of httpbin: a server you point HTTP clients at when you need a predictable, controllable other end. It echoes back what you send (/get, /post, /anything, /headers), returns any status code you ask for (/status/418), and has endpoints for redirects, delays, slow drips, streams and server-sent events, gzip, deflate and brotli responses, cookies, caching headers, images, auth challenges and a websocket echo. Use it to test HTTP clients and SDKs, retry and timeout handling, proxies, API gateways, load balancer health checks and CI pipelines, without depending on a shared public service.

The cloudimg image locks go-httpbin down:

  • HTTPS from the first boot. nginx serves go-httpbin on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS.
  • A password gate in front of everything. go-httpbin has no login of its own and reflects whatever it is sent, so the whole service sits behind an HTTP Basic Auth prompt whose password is generated on your instance's first boot. A fresh instance is never an open reflector on the internet.
  • No default login. The image contains no password. First boot writes the gate's user name and a random 24 character password to /var/lib/cloudimg/go-httpbin-credentials.txt (root only); nginx stores only a SHA-512 crypt hash. Common guesses are refused.
  • The gate password never reaches go-httpbin. nginx removes the Authorization header before passing a request on, so /headers, /anything and /dump/request can never echo your password back, and go-httpbin never logs it.
  • Real client address and host name. go-httpbin sees the address the connection really came from and the instance's configured name, whatever X-Forwarded-For, Host or Forwarded headers a request carries.
  • Redirects stay on the instance. /redirect-to only sends a client to a path on this instance or to this instance's own name, so it cannot be used as an open redirector. Step 7 shows how to allow other names.
  • Bounded requests. Request and response bodies are capped at 1 MiB and every timed endpoint at 10 seconds; nginx also limits each client address to 64 connections and 50 requests a second.
  • Other web sites cannot act with your login. A browser re-sends the gate password to any site that asks, so nginx refuses every state changing request (POST, PUT, DELETE and the like) and every WebSocket upgrade whose browser Origin is not this instance.
  • No route to the instance metadata service. nginx and go-httpbin are blocked at the kernel level from the EC2 instance metadata service (IPv4 and IPv6).
  • A current, checked build. go-httpbin 2.26.0 is the release that fixes a websocket denial of service in 2.13 to 2.25. It is built on the image from the official source with Go 1.27.3, which also fixes a crypto/x509 advisory in the Go release the official binary was built with. The build is reproducible and its checksum is pinned.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: go-httpbin 2.26.0 (/usr/local/bin/go-httpbin, listening on 127.0.0.1:8080 only), nginx, 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.
  • A security group that allows TCP 22 (SSH), TCP 443 (go-httpbin) and TCP 80 (redirects to HTTPS). Open them to the networks your clients and test runners use. go-httpbin needs no other port.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick go-httpbin 2.26.0 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large, your key pair, a security group as above and a subnet.

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=go-httpbin}]'

Step 2: Connect to your instance

Version SSH user
go-httpbin 2.26.0 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services

First boot sets the host name and certificate and creates the gate password, then go-httpbin and nginx start:

systemctl is-active go-httpbin nginx go-httpbin-firstboot
sudo test -f /var/lib/cloudimg/go-httpbin-firstboot.done && echo FIRSTBOOT_DONE

All three units are active and the first boot marker is present:

active
active
active
FIRSTBOOT_DONE

Only SSH and nginx listen on the network. go-httpbin listens on the loopback address:

sudo ss -lntp | awk 'NR>1 {print $4, $6}' | grep -v '127.0.0.5' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g' | sort
0.0.0.0:22 users:(("sshd"),("systemd"))
0.0.0.0:443 users:(("nginx"),("nginx"),("nginx"))
0.0.0.0:80 users:(("nginx"),("nginx"),("nginx"))
127.0.0.1:8080 users:(("go-httpbin"))
[::]:22 users:(("sshd"),("systemd"))
[::]:443 users:(("nginx"),("nginx"),("nginx"))
[::]:80 users:(("nginx"),("nginx"),("nginx"))

The built-in self-test checks the whole path through the gate:

sudo go-httpbin-selftest
SELFTEST-OK https 301 + healthz + gate 401 + 6 wrong gate credentials refused + /get echo + no Authorization reaches go-httpbin + 418 + /json + local redirect 302 + off-site redirect refused + 403 cross-origin POST + 415 compressed body + 444 unknown host + preflight

Step 4: Get your URL and password

sudo cat /var/lib/cloudimg/go-httpbin-credentials.txt

The file is readable by root only and holds the URL and the gate's user name and password for this instance:

# go-httpbin 2.26.0 - generated on first boot by go-httpbin-firstboot.service
# These credentials are unique to this instance; none was baked into the image. Keep this file secret.

GO_HTTPBIN_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
GO_HTTPBIN_USERNAME=httpbin
GO_HTTPBIN_PASSWORD=<GO_HTTPBIN_PASSWORD>
#
# Point any HTTP client at GO_HTTPBIN_URL with HTTP Basic Auth GO_HTTPBIN_USERNAME / GO_HTTPBIN_PASSWORD (self-signed
# certificate minted on this instance's first boot; port 80 redirects to https), e.g. GET /get, /status/418, /json.
# The gate's Authorization header is removed before go-httpbin, so its echo endpoints never show this password.
# /redirect-to may only target this instance's name (add names to /etc/cloudimg/go-httpbin-redirect-domains, then
# sudo go-httpbin-set-hostname --refresh). Self-test: sudo go-httpbin-selftest
# Change the served name: sudo go-httpbin-set-hostname <your-domain>
# User guide: https://www.cloudimg.co.uk/guides/go-httpbin-2-26-0-on-ubuntu-24-04-aws/

The URL uses the instance's public DNS name from EC2 instance metadata. Requests to https://<public-ip>/ are redirected to that name.

From your own machine or a CI job, pass the user name and password as HTTP Basic Auth, for example curl -k -u 'httpbin:<password>' https://<GO_HTTPBIN_URL host>/get. The certificate is self signed, so either trust it (copy /etc/ssl/go-httpbin/go-httpbin.crt to your client) or use your own certificate (Step 9). Opening the URL in a browser shows go-httpbin's endpoint index after the password prompt.

Step 5: Send your first requests

The commands below run on the instance itself. They read the password from the credentials file with sudo and hand it to curl as a config file (-K), so it never appears on a command line. /get echoes your request back as JSON:

H=$(sudo sed -n 's#^GO_HTTPBIN_URL=https://\([^/]*\)/$#\1#p' /var/lib/cloudimg/go-httpbin-credentials.txt)
gate() { sudo awk -F= '/^GO_HTTPBIN_USERNAME=/{u=$2} /^GO_HTTPBIN_PASSWORD=/{p=$2} END{printf "user = \"%s:%s\"\n", u, p}' /var/lib/cloudimg/go-httpbin-credentials.txt; }
curl -sk -K <(gate) --resolve "$H:443:127.0.0.1" "https://$H/get?team=qa"
{
  "args": {
    "team": [
      "qa"
    ]
  },
  "headers": {
    "Accept": [
      "*/*"
    ],
    "Host": [
      "ec2-<public-ip-dashed>.<region>.compute.amazonaws.com"
    ],
    "User-Agent": [
      "curl/8.5.0"
    ],
    "X-Forwarded-For": [
      "127.0.0.1"
    ],
    "X-Forwarded-Proto": [
      "https"
    ],
    "X-Real-Ip": [
      "127.0.0.1"
    ]
  },
  "method": "GET",
  "origin": "127.0.0.1",
  "url": "https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/get?team=qa"
}

origin and X-Forwarded-For are the address the request really came from (here the instance itself); from your machine they show your address. There is no Authorization header: nginx checked the password and removed it.

Ask for a status code, post a JSON body, and list the headers go-httpbin receives:

H=$(sudo sed -n 's#^GO_HTTPBIN_URL=https://\([^/]*\)/$#\1#p' /var/lib/cloudimg/go-httpbin-credentials.txt)
gate() { sudo awk -F= '/^GO_HTTPBIN_USERNAME=/{u=$2} /^GO_HTTPBIN_PASSWORD=/{p=$2} END{printf "user = \"%s:%s\"\n", u, p}' /var/lib/cloudimg/go-httpbin-credentials.txt; }
curl -sk -K <(gate) --resolve "$H:443:127.0.0.1" -o /dev/null -w '%{http_code}\n' "https://$H/status/418"
curl -sk -K <(gate) --resolve "$H:443:127.0.0.1" -H 'Content-Type: application/json' --data '{"order": 42}' "https://$H/post" | python3 -c 'import json,sys; o=json.load(sys.stdin); print(o["json"], o["headers"]["Content-Type"])'
curl -sk -K <(gate) --resolve "$H:443:127.0.0.1" "https://$H/headers" | python3 -c 'import json,sys; print(sorted(json.load(sys.stdin)["headers"]))'
418
{'order': 42} ['application/json']
['Accept', 'Host', 'User-Agent', 'X-Forwarded-For', 'X-Forwarded-Proto', 'X-Real-Ip']

The endpoint index at the root URL lists everything go-httpbin answers; the go-httpbin README describes each one.

Step 6: Delays, streams and limits

Timed endpoints wait as long as you ask, up to 10 seconds, and sized endpoints return up to 1 MiB. Beyond that go-httpbin answers 400 and says why:

H=$(sudo sed -n 's#^GO_HTTPBIN_URL=https://\([^/]*\)/$#\1#p' /var/lib/cloudimg/go-httpbin-credentials.txt)
gate() { sudo awk -F= '/^GO_HTTPBIN_USERNAME=/{u=$2} /^GO_HTTPBIN_PASSWORD=/{p=$2} END{printf "user = \"%s:%s\"\n", u, p}' /var/lib/cloudimg/go-httpbin-credentials.txt; }
curl -sk -K <(gate) --resolve "$H:443:127.0.0.1" -o /dev/null -w '%{http_code}\n' "https://$H/delay/2"
curl -sk -K <(gate) --resolve "$H:443:127.0.0.1" "https://$H/delay/11"; echo
curl -sk -K <(gate) --resolve "$H:443:127.0.0.1" "https://$H/bytes/2000000"; echo
curl -sk -K <(gate) --resolve "$H:443:127.0.0.1" "https://$H/drip?duration=2&numbytes=5&code=200"; echo
200
{
  "status_code": 400,
  "error": "Bad Request",
  "detail": "invalid duration: duration 11s longer than 10s"
}

{
  "status_code": 400,
  "error": "Bad Request",
  "detail": "invalid byte count: 2000000 not in range [1, 1048576]"
}

*****

nginx passes responses through as go-httpbin produces them, so /drip, /stream/<n> and /sse arrive gradually, as a real slow server's would. A request body larger than 1 MiB gets a 413 from nginx, and a client that opens more than 64 connections or sends more than 50 requests a second (after a burst of 200) gets 429.

The limits are go-httpbin's -max-body-size and -max-duration options in /etc/systemd/system/go-httpbin.service. To raise them, run sudo systemctl edit --full go-httpbin, change the two values, and change client_max_body_size in /etc/nginx/sites-available/cloudimg-go-httpbin to match; then sudo systemctl restart go-httpbin and sudo systemctl reload nginx.

Step 7: Redirects

/redirect/<n>, /relative-redirect/<n> and /absolute-redirect/<n> redirect within the instance. /redirect-to?url= redirects wherever the url says, which on an open server makes it an open redirector, so here it only accepts a path on this instance or this instance's own name:

H=$(sudo sed -n 's#^GO_HTTPBIN_URL=https://\([^/]*\)/$#\1#p' /var/lib/cloudimg/go-httpbin-credentials.txt)
gate() { sudo awk -F= '/^GO_HTTPBIN_USERNAME=/{u=$2} /^GO_HTTPBIN_PASSWORD=/{p=$2} END{printf "user = \"%s:%s\"\n", u, p}' /var/lib/cloudimg/go-httpbin-credentials.txt; }
curl -sk -K <(gate) --resolve "$H:443:127.0.0.1" -o /dev/null -w '%{http_code} %header{location}\n' "https://$H/redirect-to?url=/get"
curl -sk -K <(gate) --resolve "$H:443:127.0.0.1" -o /dev/null -w '%{http_code} %header{location}\n' "https://$H/redirect/2"
curl -sk -K <(gate) --resolve "$H:443:127.0.0.1" -o /dev/null -w '%{http_code}\n' "https://$H/redirect-to?url=https://example.com/"
302 /get
302 /relative-redirect/1
400

To let /redirect-to send clients to other names (for example the test system your client should end up at), list them one per line in /etc/cloudimg/go-httpbin-redirect-domains and re-apply the configuration. Both nginx and go-httpbin then accept them:

echo '<your-domain>' | sudo tee -a /etc/cloudimg/go-httpbin-redirect-domains
sudo go-httpbin-set-hostname --refresh

Only names are accepted, one per line, without a scheme, path or wildcard.

Step 8: Auth challenge endpoints

/basic-auth/<user>/<password>, /hidden-basic-auth/..., /digest-auth/... and /bearer test how a client answers an authentication challenge, which uses the same Authorization header as the gate. Through the gate they always answer 401, because nginx removes that header. Run them from the instance (or through an SSH tunnel to 127.0.0.1:8080), where go-httpbin answers directly:

curl -s -u alice:s3cret http://127.0.0.1:8080/basic-auth/alice/s3cret
curl -s -o /dev/null -w '%{http_code}\n' -u alice:wrong http://127.0.0.1:8080/basic-auth/alice/s3cret
{
  "authenticated": true,
  "user": "alice",
  "authorized": true
}
401

From your machine, ssh -i <your-key.pem> -L 8080:127.0.0.1:8080 ubuntu@<public-ip> makes http://127.0.0.1:8080/ on your machine reach go-httpbin directly while the tunnel is open.

Step 9: Use your own domain

Point a DNS record for your domain at the instance (an Elastic IP keeps the address stable), then give go-httpbin the name. The helper mints a new certificate for it, pins nginx, the cross-site guard and the redirect allow list to it, and redirects the instance's IP addresses there:

sudo go-httpbin-set-hostname <your-domain>

For a certificate your clients trust, replace /etc/ssl/go-httpbin/go-httpbin.crt and /etc/ssl/go-httpbin/go-httpbin.key with your own (for example from Let's Encrypt or your certificate authority) and run sudo systemctl reload nginx.

Step 10: Security notes

nginx and go-httpbin are blocked from the instance metadata service:

for u in nginx go-httpbin; do printf '%s ' "$u"; systemctl show -p IPAddressDeny --value "$u" | tr ' ' '\n' | sort | tr '\n' ' '; echo; done
nginx 168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128 
go-httpbin 168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128 

The gate answers before go-httpbin does, nginx refuses a state changing request that another web site's page sends, a request body with a Content-Encoding is refused, and a request for a host name the instance does not serve gets no answer at all:

H=$(sudo sed -n 's#^GO_HTTPBIN_URL=https://\([^/]*\)/$#\1#p' /var/lib/cloudimg/go-httpbin-credentials.txt)
gate() { sudo awk -F= '/^GO_HTTPBIN_USERNAME=/{u=$2} /^GO_HTTPBIN_PASSWORD=/{p=$2} END{printf "user = \"%s:%s\"\n", u, p}' /var/lib/cloudimg/go-httpbin-credentials.txt; }
curl -sk -o /dev/null -w '%{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/get"
curl -sk -K <(gate) -o /dev/null -w '%{http_code}\n' --resolve "$H:443:127.0.0.1" -X POST -H 'Origin: https://evil.example' --data 'x=1' "https://$H/post"
printf 'x' | curl -sk -K <(gate) -o /dev/null -w '%{http_code}\n' --resolve "$H:443:127.0.0.1" -H 'Content-Encoding: gzip' --data-binary @- "https://$H/post"
curl -sk -o /dev/null -w '%{http_code}\n' --resolve evil.example:443:127.0.0.1 https://evil.example/ || true
401
403
415
000

How go-httpbin itself is started:

systemctl show -p ExecStart --value go-httpbin | grep -o 'argv\[\]=[^;]*' | sed 's/argv\[\]=//'
/usr/local/bin/go-httpbin -host 127.0.0.1 -port 8080 -max-body-size 1048576 -max-duration 10s -log-level OFF -allowed-redirect-domains ${GO_HTTPBIN_REDIRECT_DOMAINS} 

Other notes:

  • Cross-site requests. Requests without a browser Origin header (curl, SDKs, test runners) are unaffected by the cross-site guard. go-httpbin answers CORS requests from any origin, because that is part of what it tests, so a page on another site that your browser opens while it remembers the gate password can read go-httpbin's GET responses. Those responses only reflect that page's own request (the password is removed first). Such a page cannot send POST, PUT, PATCH, DELETE or preflight (OPTIONS) requests, or open the websocket, with your login.
  • Compressed request bodies. nginx answers 415 to any request that carries a Content-Encoding, before go-httpbin sees it. Send bodies uncompressed; go-httpbin's compressed responses (/gzip, /deflate, /brotli) are unaffected.
  • WebSocket echo. /websocket/echo works through nginx over HTTP/1.1 (wss://<your host>/websocket/echo with the Basic Auth header), with messages up to 1 MiB and a 10 second session.
  • Logs. go-httpbin's own request log is off, so the URLs and headers your tests send are not kept in the system journal. nginx records each request (client address, method, path, status, user agent) in /var/log/nginx/access.log. To turn go-httpbin's log on, change -log-level OFF to -log-level INFO with sudo systemctl edit --full go-httpbin.
  • What go-httpbin exposes. /env is empty, /hostname returns a placeholder and /version only the service name, so nothing about the instance is published. go-httpbin makes no outbound requests of its own.
  • The build. /usr/share/doc/cloudimg-go-httpbin/BUILD-INFO.txt records the source archive, the Go toolchain and the checksums of the shipped files.

Step 11: Keep it up to date

Ubuntu security updates install automatically. To apply all updates now:

sudo apt-get update && sudo apt-get upgrade -y

New go-httpbin releases arrive as new versions of this listing, built and checked the same way. go-httpbin keeps no data, so moving to one is a matter of launching the new version and pointing your clients at it. The release notes list what changed.

Support and licensing

go-httpbin is open source under the MIT licence (Will McCutchen and the go-httpbin contributors); the source code is at github.com/mccutchen/go-httpbin. It has no dependencies beyond the Go standard library, whose BSD style licence ships with it. The licence texts are in /usr/share/doc/cloudimg-go-httpbin/licences/. There is no per user licence fee. The AMI price covers cloudimg's build, hardening, first boot setup and support.

  • Email: support@cloudimg.co.uk
  • Website: https://www.cloudimg.co.uk
  • Support: 24/7, with a 24 hour response SLA

The other versions on this listing are on the developer and API tools guide.