http-https-echo: a container that answers every request with a JSON view of itself
Docker image that echoes request data as JSON; listens on HTTP/S, useful for debugging.
At a glance
- What is it?
- mendhak/http-https-echo is a Docker image that echoes HTTP request properties back as JSON, on both HTTP and HTTPS, with switches for decoding a JWT header, trusting extra proxies, choosing the response status and body, and exposing Prometheus metrics. It is meant for debugging a web setup rather than for anything else.
- Who is it for?
- http-https-echo fits a developer who needs to see exactly what a client, a proxy or a load balancer actually sends, and who wants that view as JSON they can pipe into jq. It does not fit a public-facing service, because it returns request details and can be configured to return environment variables.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 10 days ago.
- What is it written in?
- Mainly Shell, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the container does and how to start it
The mendhak/http-https-echo image echoes various HTTP request properties back to the client in the response, as well as in the Docker container logs, and it comes with options that can manipulate the response output.
The basic usage is one run command, publishing both listeners:
docker run -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:42Or with Docker Compose, which the repository ships as a docker-compose.yml at the root. Then you issue a request through a browser or curl and watch the response and the container log output:
curl -k -X PUT -H "Arbitrary:Header" -d aaa=bbb https://localhost:8443/hello-worldThe two ports are the whole point of the name. 8080 is cleartext HTTP and 8443 is HTTPS with a self-signed certificate, so you can test a client's TLS behaviour without setting up a certificate authority.
The image is published on Docker Hub as mendhak/http-https-echo and on the GitHub Container Registry as ghcr.io/mendhak/http-https-echo, and the README asks you not to use the latest tag because it will break without warning, so pin a version such as :42. It is executed as non root by default and is fully compliant with Kubernetes or OpenShift deployment.
Ports, certificates and trusted proxies
The internal ports are configurable through HTTP_PORT and HTTPS_PORT, so the container can listen on something else and you map it to whatever host port you like:
docker run -e HTTP_PORT=8888 -e HTTPS_PORT=9999 -p 8080:8888 -p 8443:9999 --rm -t mendhak/http-https-echo:42The certificates live at /app/fullchain.pem and /app/testpk.pem, and you can substitute your own by volume mounting them, which is the difference between a debugging tool you can put in front of a real client and one that only speaks to curl with the insecure flag.
There is a second way to point the container at a certificate, through the HTTPS_CERT_FILE and HTTPS_KEY_FILE environment variables, which define the location of an existing certificate and private key inside the container.
The proxy setting is the one that matters for correctness rather than convenience. By default, Express is configured to trust forwarded proxy headers from loopback, link-local and private IP ranges, and ADDITIONAL_TRUSTED_PROXIES takes a single value or a comma-separated list to widen that:
docker run -e ADDITIONAL_TRUSTED_PROXIES="2001:db8::/32,203.0.113.10" -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:42That setting decides whether the client IP the container reports is the real one or the proxy's, and it accepts both an IPv6 prefix and a single address, which is what you need when the proxy sits outside the private ranges.
HTTP/1.1 and HTTP/2, and where the negotiated version shows up
The server can serve HTTP/2 in addition to HTTP/1.1, with the default being HTTP/2 over TLS and HTTP/1.1 over cleartext, and the negotiated version is returned in the httpVersion field of the response.
Over TLS the two versions are easy to compare:
$ curl -sk https://localhost:8443/ | jq '.httpVersion'
"2.0"
$ curl -sk --http1.1 https://localhost:8443/ | jq '.httpVersion'
"1.1"
$ curl -sk --http2 https://localhost:8443/ | jq '.httpVersion'
"2.0"Over cleartext, HTTP/2 only works with the prior knowledge flag:
$ curl -sk http://localhost:8080/ | jq '.httpVersion'
"1.1"
$ curl -sk --http1.1 http://localhost:8080/ | jq '.httpVersion'
"1.1"
$ curl -sk --http2-prior-knowledge http://localhost:8080/ | jq '.httpVersion'
"2.0"The asymmetry is the interesting part. A client that negotiates HTTP/2 over cleartext through the upgrade mechanism will not get it here, because the prior knowledge flag is required, and a bug report about the container not speaking HTTP/2 is often a report about that missing flag rather than about the server.
The JavaScript side of the project explains the implementation. The package depends on http2-express for the HTTP/2 support and on express for the rest, with morgan for the request log lines and @httptoolkit/httpolyglot for protocol handling.
Decoding a JWT header and shaping the response
One feature is aimed squarely at the OAuth and API gateway debugging case. If you specify the header that contains the JWT, the echo output will contain the decoded JWT, using the JWT_HEADER environment variable:
docker run -e JWT_HEADER=Authentication -p 8080:8080 -p 8443:8443 --rm -it mendhak/http-https-echo:42The request then carries the token in that header, in either a bare or a Bearer prefixed form:
curl -k -H "Authentication: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" http://localhost:8080/And a jwt section appears in the output. The dependency list shows jsonwebtoken doing the decoding, and the project overrides the jws package to a fixed version.
The rest of the options manipulate the response rather than the request, and the README's table of contents lists them all: sending an empty response, a custom status code, setting the response Content-Type, adding a delay before the response, returning only the body, including environment variables in the response, setting CORS headers, including client certificate details for mTLS, preserving the case of headers in the response body, overriding the response body with a file, setting a maximum header size, and cookies and signed cookies.
Two of those deserve a note before you enable them. The environment variable option puts the container's environment into the response body, which includes anything you passed as a secret, and the mTLS option echoes the client certificate details, which is useful for testing mutual TLS and not useful on a network you do not control.
Logging behaviour and the Prometheus endpoint
The container writes the JSON output to the logs, and it also produces the standard Express request log lines, which look like this:
::ffff:172.17.0.1 - - [03/Jan/2022:21:31:51 +0000] "GET /xyz HTTP/1.1" 200 423 "-" "curl/7.68.0"Setting DISABLE_REQUEST_LOGS to true turns off those specific lines while the JSON output still appears:
docker run --rm -e DISABLE_REQUEST_LOGS=true --name http-echo-tests -p 8080:8080 -p 8443:8443 -t mendhak/http-https-echo:42There is also a way to keep a specific path out of the logs entirely, through the LOG_IGNORE_PATH environment variable which takes a path or a regular expression.
The metrics side is configured through a small group of variables, and the compose file in the repository shows them all set at once:
- PROMETHEUS_ENABLED=true
- PROMETHEUS_METRICS_PATH=/metrics
- PROMETHEUS_WITH_PATH=false
- PROMETHEUS_WITH_METHOD=true
- PROMETHEUS_WITH_STATUS=true
- PROMETHEUS_METRIC_TYPE=summarySo the path can be excluded from the metric labels while the method and status are kept, and the metric type is a summary rather than a histogram or counter. The express-prom-bundle dependency provides the endpoint, and it is the reason the image can be scraped like any other service in a Kubernetes cluster.
How the image is built and what it ships
The Dockerfile has two stages on node:22-alpine. The build stage installs the application with production dependencies, then generates the self-signed certificate with openssl, using a 4096 bit RSA key, sha256 and a ten year validity, with a subject of CN=my.example.com and a subjectAltName covering two DNS names and two IP addresses including the loopback. The openssl package is removed again afterwards, the package manifests are deleted, and the app directory is chowned to the node user with the key file made readable.
The final stage upgrades the base packages, removes the global node modules directory, copies the built app across, and sets HTTP_PORT=8080 and HTTPS_PORT=8443 as the defaults before exposing them. The container runs as user 1000 with the command node ./index.js. The final image also carries OCI labels for the title, description, documentation URL, source and licence.
The Node application itself is a single index.js with the package manifest naming it as the main entry point, and the start script is node index.js. The dependencies are the ones the feature list implies: express and http2-express for the server, morgan for the request logs, cookie-parser for the cookie options, jsonwebtoken for the JWT decoding, express-prom-bundle for the metrics endpoint, @httptoolkit/httpolyglot for the protocol handling, and concat-stream for assembling the response body.
There is a tests.sh at the root and a GitHub Actions build workflow on the master branch, and the licence file in the repository is MIT while the package manifest records BSD-3-Clause for the npm package itself.
Editorial conclusion
http-https-echo fits a developer who needs to see exactly what a client, a proxy or a load balancer actually sends, and who wants that view as JSON they can pipe into jq. It does not fit a public-facing service, because it returns request details and can be configured to return environment variables. Before deploying it, pin a specific version rather than the latest tag, since the README says the latest tag will break without warning, and check the options that expose the environment or the client certificate details before putting it on a shared network.
Frequently asked questions
What does the mendhak/http-https-echo image do?
It echoes HTTP request properties back to the client in the response and in the Docker container logs, listening on both HTTP and HTTPS. It is a debugging aid for seeing what a client, proxy or load balancer actually sends, with options that can manipulate the response output.
How do I run the http-https-echo container and send it a request?
Run `docker run -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:42` or bring it up with docker-compose, then issue a request such as `curl -k -X PUT -H "Arbitrary:Header" -d aaa=bbb https://localhost:8443/hello-world` and watch the response and the log output.
Can http-https-echo decode a JWT for me?
Yes. Set the JWT_HEADER environment variable to the name of the header that carries the token and the echo output includes a decoded jwt section. The token can be sent bare or in the Bearer form, and the request example in the README shows a full token value to copy.
How do I make the echo server trust my reverse proxy?
By default Express trusts forwarded proxy headers from loopback, link-local and private IP ranges. Set ADDITIONAL_TRUSTED_PROXIES to a single value or a comma-separated list of addresses and subnets, for example a IPv6 prefix and an IPv4 address, and the reported client IP becomes the real one.
Does http-https-echo serve HTTP/2 over plain HTTP?
The default is HTTP/2 over TLS and HTTP/1.1 over cleartext, and the negotiated version appears in the httpVersion field. Over cleartext, HTTP/2 only works with the prior knowledge flag, so curl needs --http2-prior-knowledge to get it, otherwise the response reports 1.1.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/mendhak-docker-http-https-echo)Was this useful?