How to Proxy TCP with TLS For Clients Lacking HostSNI For Use-cases Such as LDAP & Redis

Hey folks! I've run spent the last couple days trying to figure out the answer to the issue where Traefik cannot be used to proxy TLS for services such as LDAPS and apparently Redis.

I think I finally have an understanding of the situation and a decent workaround that I wanted to share for the benefit of the community and anybody else who runs into this issue which has been driving me crazy for the last couple days! :smile:

First off, the cause of LDAPS not working when forwarded through traefik is that LDAP clients don't send the HostSNI extension through the TLS connection. Because of this, Traefik doesn't know what cert to send to the client and it sends the Traefik default cert. This can be seen in practice when an LDAP client fails to recognize the cert, but doing a curl ldaps://my.server will work fine.

There is an open pull request and an associated issue for this in Traefik that will cause Traefik to default to sending a specific cert, if the HostSNI isn't provided:

This is the optimal solution, but it isn't merged yet, leaving us in need of a workaround that works today. Here's the solution I used.

The idea is to run a TLS proxy container that will listen publicly on your LDAPS port ( 636 ). This proxy will mount the Traefik acme.json file with all of the Traefik certs in it, and use the one you specify on the commandline to do TLS termination.

There is a security concern with the fact that we are mounting all the certificates into that proxy container, but it may be acceptable depending on your use-case and policies.

By mounting the acme.json file into the proxy, we allow Traefik to generate and renew our certificate for us, which is great!

For my use-case, I was already generating a certificate for to host the LDAP UI under, so I just re-use that certificate for the LDAPS proxy, too.

Since I couldn't find an existing proxy that would do what I wanted, I created an ultra-simple proxy that runs on Deno and is made of only 146 lines of code.

Here's the example docker-compose configuration for my proxy:

    image: denoland/deno:distroless-1.25.0
      # Mount 
      - /docker-compose-core-stack/traefik-acme-data:/acme-data:ro
      # Listen publicly on the server, you could also use a Traefik
      # TLS passthrough entrypoint instead.
      - 636:636
    # Tell Deno to run my proxy script off of GitHub
    # The arguments after the script URL are:
    # 1. The path to the acme.json file
    # 2. The port to have the proxy listen on
    # 3. The LDAP server address:port
    # 4. The domain of the certificate to use from the acme.json file
    command: >
      run -A

Again, the script is super small, and feel free to look at the source yourself on GitHub to make sure it isn't doing anything spooky :eyes:

Note: the link below is not the raw link used above in the YAML, because Discourse wouldn't make a nice preview for the raw link. Make sure to check the raw link if you really want to audit the exact script.

The server will automatically restart whenever the acme.json file changes, so if traefik renews the cert, you don't have to do anything!

I've just put this together and have barely tested it, so Caveat Emptor!

But if you've been struggling with this like I have, hopefully it helps.