Deployment guide

go-httpbin 2.26.0

Overview

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) and returns any status code you ask for (/status/418). Other endpoints cover 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.

This guide covers the go-httpbin 2.26.0 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud developer and API tools listing on AWS Marketplace. You pick it as the software version when you subscribe and launch. The image locks go-httpbin down so that a fresh instance is safe to expose.

What you get:

  • 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. The password is generated on your instance's first boot, so 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/sac/data/go-httpbin-credentials.txt (root only). nginx stores only a SHA-512 crypt hash, and common guesses are refused
  • A gate password that 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 that 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
  • Protection from other web sites. 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 these ports to the networks your clients and test runners use. go-httpbin needs no other port
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, choose your Region, and select Launch through EC2.

Choose m5.large, your key pair, a subnet and the security group described above.

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

Connect to your instance

The SSH user for go-httpbin 2.26.0 on Ubuntu 24.04 is ubuntu. Connect with your key pair:

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

Check the services

On first boot, the instance sets the host name and certificate and creates the gate password. Then go-httpbin and nginx start. Check the units and the first-boot marker:

systemctl is-active go-httpbin nginx go-httpbin-firstboot
sudo test -f /var/lib/sac/data/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 only:

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"))

Run the built-in self-test, which 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
4

Get your URL and password

Read the credentials file. Only root can read it, and it holds the URL and the gate's user name and password for this instance:

sudo cat /var/lib/sac/data/go-httpbin-credentials.txt

The output looks like this:

# 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/sac/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://secureanycloud.com/stacks/go-httpbin-2-26-0/

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. Either trust it by copying /etc/ssl/go-httpbin/go-httpbin.crt to your client, or use your own certificate (Step 9). If you open the URL in a browser, go-httpbin shows its endpoint index after the password prompt.

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.

Request /get, which echoes your request back as JSON:

H=$(sudo sed -n 's#^GO_HTTPBIN_URL=https://\([^/]*\)/$#\1#p' /var/lib/sac/data/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/sac/data/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 show the address the request really came from. Here that is the instance itself, and from your machine they show your address. There is no Authorization header, because nginx checked the password and removed it.

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

H=$(sudo sed -n 's#^GO_HTTPBIN_URL=https://\([^/]*\)/$#\1#p' /var/lib/sac/data/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/sac/data/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 endpoint.

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 those limits, go-httpbin answers 400 and says why:

H=$(sudo sed -n 's#^GO_HTTPBIN_URL=https://\([^/]*\)/$#\1#p' /var/lib/sac/data/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/sac/data/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. 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 and change the two values. Then change client_max_body_size in /etc/nginx/sites-available/sac-go-httpbin to match. Finally, run sudo systemctl restart go-httpbin and sudo systemctl reload nginx.

7

Redirects

/redirect/<n>, /relative-redirect/<n> and /absolute-redirect/<n> redirect within the instance.

/redirect-to?url= redirects wherever the url says. On an open server, that makes it an open redirector. Here it accepts only a path on this instance or this instance's own name:

H=$(sudo sed -n 's#^GO_HTTPBIN_URL=https://\([^/]*\)/$#\1#p' /var/lib/sac/data/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/sac/data/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, such as the test system your client should end up at, list them one per line in /etc/sac/go-httpbin-redirect-domains and re-apply the configuration. Both nginx and go-httpbin then accept those names:

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

Enter names only, one per line, without a scheme, path or wildcard.

8

Auth challenge endpoints

/basic-auth/<user>/<password>, /hidden-basic-auth/..., /digest-auth/... and /bearer test how a client answers an authentication challenge. These endpoints use the same Authorization header as the gate, so 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

To open a tunnel from your machine, run ssh -i <your-key.pem> -L 8080:127.0.0.1:8080 ubuntu@<public-ip>. While the tunnel is open, http://127.0.0.1:8080/ on your machine reaches go-httpbin directly.

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 creates a new certificate for the name, 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. Then run sudo systemctl reload nginx.

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, and it refuses a request body that carries a Content-Encoding. 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/sac/data/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/sac/data/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

Check 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}

Keep these notes in mind:

  • Cross-site requests. The cross-site guard does not affect requests without a browser Origin header, such as curl, SDKs and test runners. go-httpbin answers CORS requests from any origin, because that is part of what it tests. A page on another site that your browser opens while it remembers the gate password can therefore read go-httpbin's GET responses. Those responses only reflect that page's own request, because 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, run sudo systemctl edit --full go-httpbin and change -log-level OFF to -log-level INFO
  • What go-httpbin exposes. /env is empty, /hostname returns a generic value and /version returns 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/sac-go-httpbin/BUILD-INFO.txt records the source archive, the Go toolchain and the checksums of the shipped files
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 a new version means launching it 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/sac-go-httpbin/licences/.

There is no per-user licence fee. The AMI price covers SecureAnyCloud's build, hardening, first-boot setup and support.

  • Email: sales@secureanycloud.com
  • Website: https://secureanycloud.com
  • Support: 24/7, with a 24-hour response SLA

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

Deploy on AWS

Launch go-httpbin 2.26.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