Request Headers
Overview
This module adds and removes headers from HTTP requests before they are sent to your upstream service. This is useful for providing additional data to upstream services 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 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 \
--request-header-add='is-ngrok: 1' \
--request-header-add='country: ${conn.geo.country_code}' \
--request-header-remove='referrer'
tunnels:
example:
proto: http
addr: 80
request_header:
add: ["is-ngrok: 1", "country: ${conn.geo.country_code}"]
remove: ["referrer"]
ssh -R 443:localhost:80 v2@connect.ngrok-agent.com http \
--request-header-add='is-ngrok: 1' \
--request-header-add='country: ${conn.geo.country_code}' \
--request-header-remove='referrer'
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.WithRequestHeader("is-ngrok", "1"),
config.WithRequestHeader("country", "${conn.geo.country_code}"),
config.WithRemoveRequestHeader("referrer"),
),
ngrok.WithAuthtokenFromEnv(),
)
}
Go Package Docs:
const ngrok = require("@ngrok/ngrok");
(async function () {
const listener = await ngrok.forward({
addr: 8080,
authtoken_from_env: true,
request_header_add: ["is-ngrok:1", "country:${conn.geo.country_code}"],
request_header_remove: "referrer",
});
console.log(`Ingress established at: ${listener.url()}`);
})();
Javascript SDK Docs:
- https://ngrok.github.io/ngrok-javascript/interfaces/Config.html#request_header_add
- https://ngrok.github.io/ngrok-javascript/interfaces/Config.html#request_header_remove
- https://ngrok.github.io/ngrok-javascript/classes/HttpListenerBuilder.html#requestHeader
- https://ngrok.github.io/ngrok-javascript/classes/HttpListenerBuilder.html#removeRequestHeader
import ngrok
listener = ngrok.forward("localhost:8080", authtoken_from_env=True,
request_header_add=["is-ngrok:1", "country:${conn.geo.country_code}"],
request_header_remove="referrer")
print(f"Ingress established at: {listener.url()}");
Python SDK Docs:
- https://ngrok.github.io/ngrok-python/http_listener_builder.html#ngrok.HttpListenerBuilder.request_header
- [https://ngrok.github.io/ngrok-python/http_listener_builder.html#ngrok.HttpListenerBuilder.remove_request_header] (https://ngrok.github.io/ngrok-python/http_listener_builder.html#ngrok.HttpListenerBuilder.remove_request_header)
- https://ngrok.github.io/ngrok-python/index.html#full-configuration
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()
.request_header("is-ngrok", "1")
.request_header("country", "${conn.geo.country_code}")
.remove_request_header("referrer")
.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:
request:
add:
is-ngrok: "1"
country: "${conn.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
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 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: ${conn.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.
Automatic Headers
Regardless of whether you enable the request headers module or not, ngrok adds default headers to every HTTP request.
You may remove these default headers with the request headers module. For example:
ngrok http 80 --request-header-remove "X-Forwarded-For,X-Forwarded-Host,X-Forwarded-Proto"
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
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 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 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
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 service.
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. For more information about the abuse interstitial, please check out the free plan limits guide.
Reference
Configuration
Parameter | Description |
---|---|
Added Headers | A list of header names to header values. Max 5. |
Removed Headers | A list 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.
Edges
Request Headers is an HTTPS Edge module which can be applied to Routes.
Pricing
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 entering the endpoint from the client. |
${conn.bytes_out} | The number of bytes leaving an endpoint to the client. |
${conn.client_ip} | Source IP of the connection to the ngrok endpoint. |
${conn.end_ts} | Timestamp when the connection to ngrok was closed. |
${conn.server_ip} | The IP that this connection was established on. |
${conn.server_port} | The port that this connection was established on. |
${conn.start_ts} | Timestamp when the connection to ngrok was started. |
Geo IP Variables
The source of this data is subject to change. It is currently provided by MaxMind GeoIP.
${conn.geo.city} | The name of the city, in EN, where the conn.client_ip is likely to originate. |
${conn.geo.country} | The name of the country, in EN, where the conn.client_ip is likely to originate. |
${conn.geo.country_code} | The two-letter ISO country code where the conn.client_ip is likely to originate. |
${conn.geo.latitude} | The approximate latitude where the conn.client_ip is likely to originate. |
${conn.geo.longitude} | The approximate longitude where the conn.client_ip is likely to originate. |
${conn.geo.radius} | The radius in kilometers around the latitude and longitude where the conn.client_ip is likely to originate. |
${conn.geo.subdivision} | The name of the subdivision, in EN, where the conn.client_ip is likely to originate. |
HTTP Request and Response Variables
${req.content_length} | The content length of the body in bytes. This may not be present if the request does not contain a body or if the client does not specify a content length because they are streaming the body. |
${req.content_type} | The media type set in the Content-Type header for this request as a string. |
${req.content_type.parameters} | The parameters set in the Content-Type header as a key value map. |
${req.content_type.raw} | The Content-Type header for this request as a string. |
${req.cookies} | The key value map of HTTP cookie objects provided in the request. |
${req.headers} | The request headers parsed as a map of lower-case names to values. |
${req.host} | The host header field value for this request. |
${req.location} | The location header value of the request. |
${req.method} | The request method. |
${req.trailers} | The request trailers parsed as a map of lower-case names to values. |
${req.url} | The normalized full URL for this request. |
${req.url.authority} | The authority portion of the URL. |
${req.url.host} | The host for this request. |
${req.url.path} | The path for this request including the leading forward slash. |
${req.url.query} | The full query string for this request excluding the leading question mark. |
${req.url.query_params} | The request query string parsed as a map of names to values. |
${req.url.raw_path} | The un-normalized path including the leading slash for this request. |
${req.url.scheme} | The scheme for this request. |
${req.url.uri} | The URI (path and query) portion of the URL. |
${req.url.user} | The user:password portion of the URL. |
${req.user_agent} | The user-agent header value for this request. |
${req.version} | The HTTP version for this request. |
${res.content_length} | The length of the content associated with the response. |
${res.content_type} | The media type set in the Content-Type header for this response as a string. |
${res.content_type.parameters} | The parameters set in the Content-Type header for this response as a key value map. |
${res.content_type.raw} | The Content-Type header for this response as a string. |
${res.cookies} | The key value map of HTTP cookie objects provided in the response. |
${res.headers} | The response headers parsed as a map of lower-case names to values. |
${res.location} | The location header value of this response. |
${res.status_code} | The status code of this response. |
${res.trailers} | The response trailers parsed as a map of lower-case names to values. |
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.
${conn.tls.client.extensions} | Additional information added to the certificate. |
${conn.tls.client.issuer} | The issuing authority of the certificate as a string roughly following the RFC 2253 Distinguished Names syntax. |
${conn.tls.client.issuer.common_name} | Common name of the issuing authority, usually the domain name. |
${conn.tls.client.issuer.country} | Country name(s) where the issuing authority is located. |
${conn.tls.client.issuer.locality} | Locality or city of the issuing authority. |
${conn.tls.client.issuer.organization} | Name(s) of the organization that issued the certificate. |
${conn.tls.client.issuer.organizational_unit} | Division of the organization responsible for the certificate. |
${conn.tls.client.issuer.postal_code} | Postal code of the issuing authority. |
${conn.tls.client.issuer.province} | Province or state of the issuing authority. |
${conn.tls.client.issuer.street_address} | Street address of the issuing authority. |
${conn.tls.client.san} | Subject alternative names of the client certificate. |
${conn.tls.client.san.dns_names} | DNS names in the subject alternative names. |
${conn.tls.client.san.email_addresses} | Email addresses in the subject alternative names. |
${conn.tls.client.san.ip_addresses} | IP addresses in the subject alternative names. |
${conn.tls.client.san.uris} | URIs in the subject alternative names. |
${conn.tls.client.serial_number} | Unique identifier for the certificate. |
${conn.tls.client.signature_algorithm} | Algorithm used to sign the certificate. |
${conn.tls.client.subject} | The entity to whom the certificate is issued as a string roughly following the RFC 2253 Distinguished Names syntax. |
${conn.tls.client.subject.common_name} | Common name of the subject, usually the domain name. |
${conn.tls.client.subject.country} | Country name(s) where the subject of the certificate is located. |
${conn.tls.client.subject.locality} | Locality or city where the subject is located. |
${conn.tls.client.subject.organization} | Name(s) of the organization to which the subject belongs. |
${conn.tls.client.subject.organizational_unit} | Division of the organization to which the subject belongs. |
${conn.tls.client.subject.postal_code} | Postal code where the subject is located. |
${conn.tls.client.subject.province} | Province or state where the subject is located. |
${conn.tls.client.subject.street_address} | Street address where the subject is located. |
${conn.tls.client.validity.not_after} | Expiration date and time when the certificate is no longer valid. |
${conn.tls.client.validity.not_before} | Start date and time when the certificate becomes valid. |
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.
${conn.tls.cipher_suite} | The cipher suite selected during the TLS handshake. |
${conn.tls.server.extensions} | Additional information added to the certificate. |
${conn.tls.server.issuer} | The issuing authority of the certificate as a string roughly following the RFC 2253 Distinguished Names syntax. |
${conn.tls.server.issuer.common_name} | Common name of the issuing authority, usually the domain name. |
${conn.tls.server.issuer.country} | Country name(s) where the issuing authority is located. |
${conn.tls.server.issuer.locality} | Locality or city of the issuing authority. |
${conn.tls.server.issuer.organization} | Name(s) of the organization that issued the certificate. |
${conn.tls.server.issuer.organizational_unit} | Division of the organization responsible for the certificate. |
${conn.tls.server.issuer.postal_code | Postal code of the issuing authority. |
${conn.tls.server.issuer.province | Province or state of the issuing authority. |
${conn.tls.server.issuer.street_address | Street address of the issuing authority. |
${conn.tls.server.san} | Subject alternative names of the ngrok server's leaf TLS certificate. |
${conn.tls.server.san.dns_names} | DNS names in the subject alternative names of the ngrok server's leaf TLS certificate. |
${conn.tls.server.san.email_addresses} | Email addresses in the subject alternative names of the ngrok server's leaf TLS certificate. |
${conn.tls.server.san.ip_addresses} | IP addresses in the subject alternative names of the ngrok server's leaf TLS certificate. |
${conn.tls.server.san.uris} | URIs in the subject alternative names of the ngrok server's leaf TLS certificate. |
${conn.tls.server.serial_number} | Unique identifier for the certificate. |
${conn.tls.server.signature_algorithm} | Algorithm used to sign the certificate. |
${conn.tls.server.subject} | The entity to whom the certificate is issued as a string roughly following the RFC 2253 Distinguished Names syntax. |
${conn.tls.server.subject.common_name} | Common name of the subject, usually the domain name. |
${conn.tls.server.subject.country} | Country name(s) where the subject of the certificate is located. |
${conn.tls.server.subject.locality} | Locality or city where the subject is located. |
${conn.tls.server.subject.organization} | Name(s) of the organization to which the subject belongs. |
${conn.tls.server.subject.organizational_unit} | Division of the organization to which the subject belongs. |
${conn.tls.server.subject.postal_code} | Postal code where the subject is located. |
${conn.tls.server.subject.province} | Province or state where the subject is located. |
${conn.tls.server.subject.street_address} | Street address where the subject is located. |
${conn.tls.server.validity.not_after} | Expiration date and time when the certificate is no longer valid. |
${conn.tls.server.validity.not_before} | Start date and time when the certificate becomes valid. |
${conn.tls.sni} | The hostname included in the ClientHello message via the SNI extension. |
${conn.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: ${conn.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"
}
}