Terminate TLS however (and wherever) you want with Traffic Policy

We were recently told by a customer that ngrok offered the easiest to set up mTLS they'd ever experienced, which is the kind of compliment I can't just leave unmentioned.

That simplicity is thanks to our new terminate-tls Traffic Policy action, which gives you more control over where and how ngrok decrypts incoming TLS traffic and validates client certificates so you don't have to build this logic directly into your APIs or apps. These functionalities existed before, but as a mix of agent configurations and edge modules—Traffic Policy lets you put all your TLS requirements into one configuration engine.

For example, with minimal configuration, you can enforce a specific version of TLS on all TCP connections passing through your API gateway.

on_tcp_connect:
  - actions:
      - type: terminate-tls
        config:
          # Enforce v1.3 of TLS on all TCP connections
          min_version: "1.3"
          max_version: "1.3"

Your choice: TLS termination and end-to-end encryption with ngrok

By default, ngrok handles TLS connections differently based on the type of endpoint you've created.

For HTTP/S endpoints, ngrok terminates TLS connections for you at the ngrok cloud service, so you don't need to configure certificate handling yourself. We forward traffic over an encrypted tunnel to your agent so that your data stays protected in transit, even after we terminated TLS at the edge.

For TLS endpoints and TLS connections over TCP endpoints, ngrok doesn't terminate connections by default. Instead, you must configure the terminate-tls action to determine what to do with these TLS connections. You can configure ngrok to terminate TLS:

  • At our service, like HTTP/S endpoints.
  • At your ngrok agent, which creates a form of end-to-end (E2E) encryption where ngrok's network never sees the payloads that move through your endpoints. This requires extra configuration beyond the terminate-tls action.
  • At your upstream service, which takes your E2E TLS even further, where neither the ngrok network or the agent can see your traffic. This is the default with TCP or TLS connections, but does mean your upstream service must handle TLS termination.

If you do choose to terminate TLS connections at ngrok's service, we prefer the latest and most secure version, TLS 1.3, with fallback support for TLS 1.2 for broader client compatibility. The newer version only needs one round trip for the initial handshake instead of two in TLS 1.2, which shaves a few precious milliseconds off your requests, and also lets clients resume a previous connection without any handshake.

ngrok handles all certificate provisioning and renewing for you, which simplifies your networking to-do list and gives you time back to think about bigger projects you want to solve in your infrastructure. Termination at ngrok's service also accelerates your traffic by using our global load balancer to terminate connections at the closest point of presence—because TLS connections require multiple round-trips, minimizing the distance between your client and the point of presence they connect to makes a big difference in latency.

But as hard as we try to make TLS termination easy for you, we're also believers in choice and setting up your network exactly as your requirements demand—with multiple endpoint options and the terminate-tls Traffic Policy action, you have the power to do exactly that.

Terminate TLS on a TLS/TCP endpoint at ngrok’s cloud

If you can't terminate TLS connections at your upstream service, or just don't need E2E TLS, you can simplify your stack and have ngrok terminate TLS for you on our network before, as mentioned above, we forward traffic over an encrypted tunnel to your agents.

In this case, you can create a Traffic Policy configuration with the terminate-tls action enabled on all connections:

on_tcp_connect:
  - actions:
      - type: terminate-tls

When you fire up your agent to create your TLS or TCP endpoint, you'll have TLS termination turned on at ngrok's cloud, saving you from a lot of networking and certificate toil.

Use custom certificates with any type of endpoint

You can always bring your own if you can't use the certificates that ngrok provisions and renews for you with an ACME provider like Let's Encrypt. You might want to take this step if you're issuing certificates from your own certificate authority (CA) or using an extended validation (EV) certificate.

Assuming you already have a server.key and server.crt files generated from your certificate authority (CA) that you'd like to use with a specific endpoint, you can add the contents of these to the terminate-tls action under the server_private_key and server_certificate options, respectively.

on_tcp_connect:
  - actions:
      - type: terminate-tls
        config:
          server_private_key: |-
            -----BEGIN PRIVATE KEY-----
            ... private key ...
            -----END PRIVATE KEY-----
          server_certificate: |-
            -----BEGIN CERTIFICATE-----
            ... certificate ...
            -----END CERTIFICATE-----

Fire up your endpoint again—now, if clients don't already trust your CA, they'll need to include the --cacert flag with requests to provide CA certificate explicitly.

If you don't have a CA or server certificates already, our terminate-tls doc walks you through creating self-signed certificates.

Enable mTLS in a minute

With terminate-tls, you can also enable mTLS with TLS termination at the ngrok network in less than a minute. Truly.

Given that for many of you, mTLS is a Day 0 "must have" feature for your API gateway or ingress solution, we worked hard to simplify this setup experience. When other gateways or ingress providers ask you to upload certificates via an API call or restrict mTLS to enterprise-priced plans, we make it open, accessible, and composable with the rest of our Traffic Policy engine.

If you already have a CA certificate, you can paste it into your Traffic Policy file and start your endpoint to require that clients add their client.crt and client.key files to requests to enforce the whole mutual part of mTLS.

on_tcp_connect:
  - actions:
      - type: terminate-tls
        config:
          mutual_tls_certificate_authorities:
            - |
              -----BEGIN CERTIFICATE-----
              ... certificate ...
              -----END CERTIFICATE-----

Don't have a CA, server keys, or client certificates? Our docs also have details on provisioning them in five commands.

We not only make applying mTLS easy, but also flexible—you could apply this rule on an agent endpoint if you have just one service that requires it, but you could also drop it on a cloud endpoint to instantly require valid client certificates to access any of your services.

Here's what chaining mTLS and path-based routing might look like:

on_tcp_connect:
  - actions:
      - type: terminate-tls
        config:
          mutual_tls_certificate_authorities:
            - |
              -----BEGIN CERTIFICATE-----
              ... certificate ...
              -----END CERTIFICATE-----
  - expressions:
      - req.url.path.startsWith('/foo')
    actions:
      - type: forward-internal
        config:
          url: https://foo.internal
 - expressions:
      - req.url.path.startsWith('/bar')
    actions:
      - type: forward-internal
        config:
          url: https://bar.internal
  - actions:
      - type: deny
        config:
          status_code: 404

Related but also not: route based on TLS connection data

When you let ngrok handle TLS termination on your HTTP/S endpoints, you can also filter and take action on specific requests based on dozens of connection variables

SNI (Server Name Indication) is a TLS protocol extension that lets a server present multiple certificates on the same IP address and port. You can use Traffic Policy to read the SNI data on a given request and forward it to the right upstream service's internal endpoint every time.

If a request comes in with a conn.tls.sni value of foo.example.com, then this CEL interpolation routes it to https://foo.internal. Same with bar or baz or any other services you spin up, without extra work for you to get the routing just right.

on_http_request:
  - actions:
      - type: forward-internal
        config:
          # Use CEL interpolation and the split macro to extract the subdomain
          url: https://${conn.tls.sni.split(".example.com")[0]}.internal

Time to terminate some connections!

… on your terms.

Whether you're locking down an internal API, fiddling with cert chains, or just need to say "yes, we use mTLS" in a security review, terminate-tls has your back.

Your next steps:

  • Want to kick the tires? Sign up here—even free accounts can use terminate-tls.
  • Not sure what Traffic Policy is? Check out the docs.
  • Ready for a deep-dive? Explore the terminate-tls action docs for all the config goodies and action result variables.
  • Stuck? Curious? Hit up our support team—they’re scary good.
  • Want to see it live or ask us something weird about TLS termination? Join us on Office Hours livestream.

Like most things about networking, TLS is hard—but it doesn't have to be. With ngrok, doing things the right way is, once again, also the easy way.

Share this post
Joel Hans
Joel Hans is a Senior Developer Educator. Away from blog posts and demo apps, you might find him mountain biking, writing fiction, or digging holes in his yard.
API gateway
Authentication
Traffic Policy
Gateways
Features
Security
Production