Response Headers
Overview
This module adds and removes headers from the HTTP response before it is returned to the client. This is useful for stripping internal headers or enforcing the use of security headers without modifying your upstream service.
You may interpolate variables into header values to make them dynamic.
Example Usage
- Agent CLI
- Agent Config
- SSH
- Go
- Javascript
- Python
- Rust
- Kubernetes Controller
ngrok http 80 \
--response-header-add='content-security-policy: self' \
--response-header-add='dial-duration: ${.backend.dial_duration}' \
--response-header-remove='internal-trace-id'
tunnels:
example:
proto: http
addr: 80
response_header:
add:
- "content-security-policy: self"
- "dial-duration: ${.backend.dial_duration}"
remove:
- "internal-trace-id"
ssh -R 443:localhost:80 v2@connect.ngrok-agent.com http \
--response-header-add='content-security-policy: self' \
--response-header-add='dial-duration: ${.backend.dial_duration}' \
--response-header-remove='internal-trace-id'
import (
"context"
"net"
"golang.ngrok.com/ngrok"
"golang.ngrok.com/ngrok/config"
)
func ngrokListener(ctx context.Context) (net.Listener, error) {
return ngrok.Listen(ctx,
config.HTTPEndpoint(
config.WithResponseHeader("content-security-policy", "self"),
config.WithResponseHeader("dial-duration", "${.backend.dial_duration}"),
config.WithRemoveResponseHeader("internal-trace-id"),
),
ngrok.WithAuthtokenFromEnv(),
)
}
Go Package Docs:
const ngrok = require("@ngrok/ngrok");
(async function () {
const listener = await ngrok.forward({
addr: 8080,
authtoken_from_env: true,
response_header_add: [
"content-security-policy:self",
"dial-duration:${.backend.dial_duration}",
],
response_header_remove: "internal-trace-id",
});
console.log(`Ingress established at: ${listener.url()}`);
})();
Javascript SDK Docs:
- https://ngrok.github.io/ngrok-javascript/interfaces/Config.html#response_header_add
- https://ngrok.github.io/ngrok-javascript/interfaces/Config.html#response_header_remove
- https://ngrok.github.io/ngrok-javascript/classes/HttpListenerBuilder.html#responseHeader
- https://ngrok.github.io/ngrok-javascript/classes/HttpListenerBuilder.html#removeResponseHeader
import ngrok
listener = ngrok.forward("localhost:8080", authtoken_from_env=True,
response_header_add=["content-security-policy:self", "dial-duration:${.backend.dial_duration}"],
response_header_remove="internal-trace-id")
print(f"Ingress established at: {listener.url()}");
Python SDK Docs:
use ngrok::prelude::*;
async fn listen_ngrok() -> anyhow::Result<impl Tunnel> {
let sess = ngrok::Session::builder()
.authtoken_from_env()
.connect()
.await?;
let tun = sess
.http_endpoint()
.response_header("content-security-policy", "self")
.response_header("dial-duration", "${.backend.dial_duration}")
.remove_response_header("internal-trace-id")
.listen()
.await?;
println!("Listening on URL: {:?}", tun.url());
Ok(tun)
}
Rust Crate Docs:
kind: NgrokModuleSet
apiVersion: ingress.k8s.ngrok.com/v1alpha1
metadata:
name: ngrok-module-set
modules:
headers:
response:
add:
content-security-policy: "self"
dial-duration: "${.backend.dial_duration}"
remove: ["internal-trace-id"]
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
annotations:
k8s.ngrok.com/modules: ngrok-module-set
spec:
ingressClassName: ngrok
rules:
- host: your-domain.ngrok.app
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: example-service
port:
number: 80
Behavior
Variable Interpolation
You may interpolate variables into header values. Variables are interpolated
into headers with JSONPath expressions surrounded by the ${}
syntax.
For example to return to the duration spent dialing the upstream service, you may construct a header value like so.
ngrok http 80 --response-header-add 'dial-duration: ${.backend.dial_duration}'
If you are specifying variable interpolation from the command line, make sure to use single quotes for the command line argument otherwise it is likely that the shell will interpret your variable definition.
Consult the Variables Reference for the available variables.
Multiple Header Values
HTTP headers may include the same header multiple times. You may add a header multiple times with different values and it will be added multiple times. For example:
ngrok http 80 --response-header-add "foo: bar" --response-header-add "foo: baz"
will result in a header with multiple values set
HTTP/2 200
foo: bar
foo: baz
There is a bug which currently causes the above behavior not to be correct. Only the last header will be used when specifying multiple headers. This behavior will be fixed to match what is documented above.
If you remove a header that has multiple values, all values will be removed.
Replacing Header Values
If you add a header that is already present in the HTTP response, it will add another header. For example, if you run:
ngrok http 80 --response-header-add "foo: new-value"
And the HTTP response from the upstream server was:
HTTP/2
foo: original-value
The client will receive the following:
HTTP/2
foo: original-value
foo: new-value
If you wish to replace a header, you can combine header removal and addition to achieve that effect.
ngrok http 80 --response-header-remove "foo" --response-header-add "foo: new-value"
This will cause the HTTP response in this case to become:
HTTP/2
foo: new-value
Case Sensitivity
When adding headers, ngrok normalizes all header keys to a lower case representation per the http/2 RFC. See RFC 7540.
When removing headers, ngrok will remove any headers that match with a case-insensitive comparison.
Ordering
Response header changes made by other modules can be overridden by this module because this module is executed immediately before the HTTP header is written to the client.
http_request_complete.v0 events include any header changes made by this module because those events are published after this module executes.