Developer's guide: Manage traffic with CEL expressions and connection variables

You can’t get far in ngrok’s Traffic Policy Module without running into CEL expressions and variables. Together, they’re a developer-friendly way of creating policies that shape or manipulate traffic based on information embedded within every connection, request, and response that passes through your ngrok endpoint.

ngrok’s Traffic Policy Module uses Common Expression Language (CEL) variables to help you target specific traffic and dynamically apply policies based on information you’d otherwise have to write custom parsing and business logic for. Today, let’s go deeper into one collection of CEL opportunities available to help you create dynamic policies on your endpoints: connection variables.

What do connection variables do?

Connection variables contain information about the raw connection between a client and your ngrok endpoint. This includes information about the size of the connection, its geographic origin, the usage of TLS (or not!), when the connection with ngrok started, and so on. They’re different from the contents of a request or response, like the path (/api/create), method (POST vs. GET), its body, and so on—those have separate variables we’ll cover soon enough.

We’ve grouped all connection variables under the conn. namespace to help you track what phase of traffic you’re dealing with. And keep in mind the available connection variables differ between HTTP, TLS, and TCP endpoints.

Here are some examples you might find useful off the bat:

  • conn.bytes_in: The number of bytes entering your ngrok endpoint from the client.
  • conn.client_ip: The IP address of the client who connected to your ngrok endpoint.
  • conn.ts.start: The timestamp of when the connection to your ngrok endpoint began.
  • conn.geo.country_code: The two-letter ISO country code where the conn.client_ip is likely to originate.
  • conn.tls.version: The version of the TLS protocol used between the client and your ngrok endpoint.
  • conn.tls.client.issuer.common_name: The common name of the issuing authority of the TLS certificate used in the connection to your ngrok endpoint.

Connection variables, like all other CEL variables available in the Traffic Policy Module, aren’t just for filtering connections and applying policies to only a subset of what comes through your ngrok endpoint. You can also interpolate the content of any variable into the subsequent action, such as adding more detail to upstream headers or logs.

Example traffic policies using connection variables

References are one thing, but what you’re really after is seeing some of these connection variables in action. Let’s dig in.

Example: Allow traffic from only specific IPs

First, you can restrict who can access your ngrok endpoint, and thus your backend services or devices, by their IP address. localhost, in both IPV6 and IPV4, is a simple and helpful starting point:

---
inbound:
  - expressions:
      - "!conn.client_ip in ['::1', '127.0.0.1']”
    actions:
      - type: "deny"
        config:
          status_code: 404

This example looks for source IPs that are not localhost, then pitches them over to the Deny action with a 404 response. All requests from localhost will pass through unimpeded.

Example: Create geo-aware services

Next, let’s explore one of the geo-based variables… and CEL variable interpolation. Let’s say you want to localize a storefront based on just a few target countries.

---
inbound:
  - expressions:
      - "conn.geo.country_code in ['GB', 'AU', ‘MX’, ‘JP’, ‘IN’]”
      - "req.url.path.startsWith('/store/')"
    actions:
      - type: "url-rewrite"
        config:
          from: “/store/”
          to: “/store/${conn.geo.country_code}/”

In this example, you start with a similar filter against an established list of country codes you’d like to support in your upstream service. Connections that match said list, and are also pointed to the /store/ path, get redirected to a new path with the interpolated country code, letting you handle all the business logic around supporting different countries, languages, and currencies in your upstream service. Much easier and more foolproof than asking the user to select their country or hook up the frontend to use location data via the browser.

If you don’t like rewrites, you could always add a header to the request as it passes through your ngrok endpoint for your upstream service to respond to in kind.

Example: Add a custom page/response during planned maintenance

You don’t want to waste precious cycles adding some custom “we’re under maintenance” page and then manually switching between them when you’re already stressed out enough about the actual maintenance—let ngrok handle it for you with a highly customizable use of the connection timestamp.

---
inbound:
  - expressions:
      - "conn.ts.start > timestamp('2024-12-31T00:00:00Z') && conn.ts.start < timestamp('2025-01-01T06:00:00Z')"
    actions:
      - type: "custom-response"
        config:
      	  status_code: 503
          content: "<html><body><h1>Service Unavailable</h1><p>Our servers are currently down for maintenance. Please check back later.</p></body></html>"
          headers:
            content-type: "text/html"

This rule filters for any traffic arriving at your ngrok endpoint within the first 6 hours of 2025—talk about taking the whole “don’t deploy on Friday” to a whole new level! — and responds with a custom maintenance page. The request doesn’t even reach your ngrok agent or upstream server, so you’re free and clear to get your maintenance work done and enjoy the New Year.

If you’re building and maintaining an API, you can also generate a custom response using JSON.

What’s next?

First, start using connection variables to manage traffic to your apps, devices, and APIs by signing up for a free ngrok account.

From there, we’re all about giving you complete freedom to deploy ngrok and your traffic policies in whichever way works best for your development workflows. ngrok works as a standalone agent, with a Go/Rust/JavaScript SDK, or as a Kubernetes Operator, and you can configure policies in many ways on top of that, from a single .yml file to a Kubernetes manifest and beyond.

As you get started, some additional reading can’t hurt, either:

If you have feedback on how connection variables and CEL expressions work within ngrok’s Traffic Policy Module, create an issue on the ngrok community discussion repo.

Share this post
Joel Hans
Joel Hans is a Senior Developer Educator at ngrok. He has been teaching developers and engineers for a decade, and has plenty of strong thoughts about documentation, developer education, developer marketing, and much more. Away from how-to guides and demo apps, he spends his time mountain biking, bouldering, writing fiction, and spending time with his wife, two daughters, dog, and desert tortoise.
Traffic Policy
Developer
Features
Gateways
Production