Skip to main content

Request Headers


Overview

This module adds and removes headers from HTTP requests before they are sent to upstream applications. This is useful for providing additional data to upstream applications about the behavior of the ngrok edge.

Changes made to request headers will not be visible to other modules; they will only be seen by your upstream server.

You may interpolate variables into header values to make them dynamic.

Quickstart

Agent CLI

ngrok http 80 \
--request-header-add='is-ngrok: 1' \
--request-header-add='country: ${.ngrok.geo.country_code}' \
--request-header-remove='referrer'

Agent Configuration File

tunnels:
example:
proto: http
addr: 80
request_headers:
add: ["is-ngrok: 1", "country: ${.ngrok.geo.country_code}"]
remove: ["referrer"]

SSH

ssh -R 443:localhost:80 connect.ngrok-agent.com http \
--request-header-add='is-ngrok: 1' \
--request-header-add='country: ${.ngrok.geo.country_code}' \
--request-header-remove='referrer'

Go SDK

import (
"context"
"net"

"golang.ngrok.com/ngrok"
"golang.ngrok.com/ngrok/config"
)

func listenRequestHeaders(ctx context.Context) net.Listener {
listener, _ := ngrok.Listen(ctx,
config.HTTPEndpoint(
config.WithRequestHeader("is-ngrok", "1"),
config.WithRequestHeader("country", "${.ngrok.geo.country_code}"),
config.WithRemoveRequestHeader("referrer"),
),
ngrok.WithAuthtokenFromEnv(),
)

return listener
}

Rust SDK

use ngrok::prelude::*;

async fn start_tunnel() -> anyhow::Result<impl Tunnel> {
let sess = ngrok::Session::builder()
.authtoken_from_env()
.connect()
.await?;

let tun = sess
.http_endpoint()
.request_header("is-ngrok", "1")
.request_header("country", "${.ngrok.geo.country_code}")
.remove_request_header("referrer")
.listen()
.await?;

println!("Listening on URL: {:?}", tun.url());

Ok(tun)
}

Kubernetes Ingress Controller

kind: NgrokModuleSet
apiVersion: ingress.k8s.ngrok.com/v1alpha1
metadata:
name: ngrok-module-set
modules:
headers:
request:
add:
is-ngrok: "1"
country: "${.ngrok.geo.country_code}"
remove: ["referrer"]
---
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

Edges

Request Headers is a supported HTTP Edge Route module.

Behavior

Ordering

Request header changes made by other modules can be overridden by this module because this module is executed immediately before the HTTP request is transmitted to the upstream application.

Variable Interpolation

You may interpolate variables into header values. Variables are interpolated into headers with JSONPath expressions surrounded by the ${} syntax.

For example to include geographical data about the client IP that initiated the request, you may construct a header value like so.

ngrok http 80 --request-header-add 'country: ${.ngrok.geo.country_code}'

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 --request-header-add "foo: bar" --request-header-add "foo: baz"

will result in a header with multiple values set

GET / HTTP/1.1
host: example.ngrok.app
foo: bar
foo: baz
note

There is a bug which currently causes the above behavior to not be correct. Only the last header to 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 request, it will add another header. For example, if you run:

ngrok http 80 --request-header-add "foo: new-value"

And the HTTP request was already:

GET / HTTP/1.1
host: example.ngrok.app
foo: original-value

The client will receive the following:

GET / HTTP/1.1
host: example.ngrok.app
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 --request-header-remove "foo" --request-header-add "foo: new-value"

This will cause the HTTP request in this case to become:

GET / HTTP/1.1
host: example.ngrok.app
foo: new-value

Case Sensitivity

When adding headers, ngrok normalizes all header keys to a lower case reprsentation per the http/2 RFC. See RFC 7540.

When removing headers, ngrok will remove any headers that match with a case-insensitive comparison.

Automatic Headers

Regardless of whether you enable the request headers module or not, ngrok adds two headers to every HTTP request that is processed by the ngrok edge.

HeaderDescription
X-Forwarded-ForThe IP address of the client who initiated the request to the ngrok edge.
X-Forwarded-ProtoEither http or https, indicating the protocol the ngrok edge received the request on.

You may remove these headers with the request headers module.

Special Cases

  • You may not add or remove the user-agent header.
  • You may not remove the host header without adding it with a different value.
  • Adding a host header will replace the existing value instead of adding a second value.
  • You may not use this module to add the ngrok-skip-browser-warning header to skip the ngrok browser warning on free accounts.

Reference

Configuration

ParameterDescription
Added HeadersA list of of header names to header values. Max 5.
Removed HeadersA list of of header names to remove. Max 5.

Upstream Headers

This module adds the configured headers to requests sent to the upstream application.

Errors

This module does not return any errors.

Events

This module does not populate any fields in events.

Limits

This module is available on all plans.

Variables

ngrok makes variables available for interpolation into headers.

Some variables will only be populated with values if you have configured the corresponding module for your endpoint, otherwise they will be empty. For example, the variable ${.basic_auth.username} is only available if you have enabled the basic auth module on your endpoint.

Backend Variables

${.backend.connection_reused}True if ngrok reused a TCP connection to transmit the HTTP request to the upstream service.
${.backend.dial_duration}The time to establish a connection from ngrok to the agent.
${.backend.id}This is the ngrok ID of the backend that serviced this request. This is empty if the endpoint is not handled by an Edge.

Basic Auth Variables

These variables are only populated when using the Basic Auth module.

${.basic_auth.decision}allow if the request successfully authenticated via the Basic Auth module, block otherwise.
${.basic_auth.username}If the request successfully authenticated via the Basic Auth module, this is the username that authenticated.

Circuit Breaker Variables

These variables are only available when using the Circuit Breaker module.

${.circuit_breaker.decision}Whether the HTTP request was sent to the upstream service. allow if the breaker was closed, block if the breaker was open, allow_while_open if the request was allowed while the breaker is open.

Compression Variables

These variables are only populated when using the Compression module.

${.compression.algorithm}The compression algorithm used to encode responses from the endpoint. Either gzip, deflate, or none.
${.compression.bytes_saved}The difference between the size of the raw response and the size of the response as compressed by ngrok.

Connection Variables

${.conn.bytes_in}The number of bytes arriving at an endpoint from the frontend.
${.conn.bytes_out}The number of bytes leaving an endpoint to the frontend.
${.conn.client_ip}The source IP of the TCP connection to the ngrok edge.
${.conn.duration}The total duration (in seconds) of the TCP connection between the ngrok endpoint and the agent.
${.conn.end_ts}The timestamp when the TCP connection to the ngrok edge is closed.
${.conn.id}The ngrok ID for this TCP connection.
${.conn.server_ip}The IP address of the server that received the request.
${.conn.server_name}The hostname associated with this connection.
${.conn.server_port}The port that the connection for this request came in on.
${.conn.start_ts}The timestamp when the TCP connection to the ngrok edge is established.

Geo IP Variables

The source of this data is subject to change. It is currently provided by MaxMind GeoIP.

${.ngrok.geo.country_code}This is the two-letter ISO country code based on the client IP.
${.ngrok.geo.lat_long_radius_km}This is the radius in kilometers around the latitude and longitude where the client IP is likely to originate.
${.ngrok.geo.latitude}This is the approximate latitude based on the client IP.
${.ngrok.geo.longitude}This is the approximate longitude based on the client IP.

HTTP Variables

${.http.request_id}The unique ngrok-assigned ID of this request.
${.http.request.method}The request method, normalized to uppercase.
${.http.request.url.host}The host component of the request URL.
${.http.request.url.path}The path component of the request URL
${.http.request.url.query}The query string component of the request URL
${.http.request.url.raw}The full URL of the request including scheme, host, path, and query string.
${.http.request.url.scheme}The scheme component of the request URL.
${.http.request.user_agent}The value of the User-Agent header in the request received by ngrok edge.
${.http.request.version}The HTTP version used in the request.
${.http.response.status_code}The status code of the response returned by the ngrok edge.

IP Restrictions Variables

These variables are only populated when using the IP Restrictions module.

${.ip_policy.decision}allow if IP Policy module permitted the request to the upstream service, block otherwise.
${.ip_policy.matching_rule}The rule that triggered an IP Policy match on the endpoint.

Mutual TLS

These variables are only populated when using the Mutual TLS module.

${.tls.client_cert.serial_number}The serial number of the client's leaf TLS certificate in the Mutual TLS handshake.
${.tls.client_cert.subject.cn}The subject common name of the client's leaf TLS certificate in the Mutual TLS handshake.
${.tls.client_cert.subject}The subject of the client's leaf TLS certificate in the Mutual TLS handshake

ngrok Variables

${.ngrok.client_ip}This is the original client IP of the request.
${.ngrok.request_id}This is the unique request ID generated by ngrok

OAuth Variables

These variables are only populated when using the OAuth module.

${.oauth.app_client_id}The is the ID of the OAuth2 application used to handle this request.
${.oauth.decision}'allow' if the OAuth module permitted the request to the upstream service, 'block' otherwise.
${.oauth.user.email}This is the email address of the user that was authenticated.
${.oauth.user.id}The authenticated user's ID returned by the OAuth provider.
${.oauth.user.name}The authenticated user's name returned by the OAuth provider.

OpenID Connect Variables

These variables are only populated when using the OpenID Connect module. These variables are identical to the OAuth Variables.

${.oauth.app_client_id}The is the ID of the OAuth application used to handle this request.
${.oauth.decision}allow if the OpenID Connect module permitted the request to the upstream service, block otherwise.
${.oauth.user.email}This is the email address of the user that was authenticated.
${.oauth.user.id}The authenticated user's ID returned by the OpenID Connect provider.
${.oauth.user.name}The authenticated user's name returned by the OpenID Connect provider.

SAML Variables

These variables are only populated when using the SAML module.

${.ngrok.saml.subject}The SAML subject of the the authenticated user.

TLS Termination Variables

These variables are only populated on requests to HTTPS endpoints.

${.tls.cipher_suite}The cipher suite selected during the TLS handshake.
${.tls.version}The version of the TLS protocol used between the client and the ngrok edge.

Webhook Verification Variables

These variables are only populated when using the Webhook Verification module.

${.webhook_validation.decision}'allow' if the Webhook Verification module permitted the request to the upstream service, 'block' otherwise.

Try it out

To try out the request headers module we're going to foward to httpbin.

ngrok http https://httpbin.org --domain your-domain.ngrok.app

In another terminal, curl that endpoint:

curl https://your-domain.ngrok.app/headers

{
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip",
"Host": "your-domain.ngrok.app",
"User-Agent": "curl/7.85.0",
"X-Amzn-Trace-Id": "Root=1-64d939d7-638343a031ac3f895e36af65"
}
}

Now, let's try manipulating the headers. We'll remove the user-agent header and add a header of our own with geo data. Stop your previous instance of ngrok with Ctrl+C and then restart ngrok with a new command.

ngrok http https://httpbin.org \
--domain your-domain.ngrok.app \
--request-header-remove='user-agent' \
--request-header-add='country: ${.ngrok.geo.country_code}'

Then curl your endpoint again to see how the request headers have been updated:

curl https://your-domain.ngrok.app/headers

{
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip",
"Country": "US",
"Host": "your-domain.ngrok.app",
"X-Amzn-Trace-Id": "Root=1-64d93b73-689c799b056568ff13546ef4"
}
}