I want to set up sslh on port 443 to forward to https and openvpn which reside in two docker containers. Since I want to provide IP based ACLs on https in the long run, I want sslh to provide the true client IP to the webserver (aka sslh's "transparent" mode).

As I understand the documentation and this answer, sslh will fake outside requests on the internal network and needs to be fed back the servers answer to translate the port for communication with the client.

My problem is the iptables setup. There are two possibilities where to run sslh: on the host or in a container inside the docker network.

I managed to get the "default" setup as described in the documentation to work, i.e.: sslh on the host could transparently forward to a service on the host.

However, I cannot get transparent sslh either on the host or in a container to work with the containerized servers. More specifically, my iptables rules to mark the traffic that should be routed to sslh never match a packet.

E.g. for an https webserver I would:

iptables -t mangle -A OUTPUT -p tcp --sport 443 --source --jump SSLH

Using this chart I tried to discern how the virtual ethernet devices of the container, the docker bridge, the docker NAT and iptables work together but I can't wrap my head around it.

To better discuss this let us assume the following:

  • host (eth0):
  • docker-bridge (br0):
    • webserver (veth1): (exposed on host as 8443 if necessary)
    • openvpn (veth2): (forwarded to host if necessary)
    • sslh in container (mutually exclusive with host sslh): (forwarded to host)
  • sslh on host (mutually exclusive with container sslh): port 443
  • client:

Here is my failing model of should happen when a browser connects to host:443 and sslh is on the host in transparent mode

  • sslh connects to posing as the original client
  • the webserver answers for the TCP handshake to via veth1
  • the packet goes onto br0
  • the routing decides to send it via ent0
  • iptables' mangle output chain marks it as traffic for sslh (this is what does not work for me)
  • the mark indicates to route the packet to the local lo device instead
  • sslh picks it up, translates the port and sends it back to the client

My problem in the above list is, that I don't know the filter criteria for the webservers outbound traffic: Should I use the docker internal port 443 or the mapped port 8443? The docker IP or another one? It boils down to: will by mangle output rule run before or after docker's NAT?

The I thought I could put sslh into the docker network to avoid thinking about the NAT but I still can't get the iptables rules to match.

I am at a loss how to proceed.


I know that this thread is rather "old", but I just ran into the same situation and I was literally lost in the beginning. But I managed to make it somehow work. It might not be the most elegant solution though (feel free to post suggestions!)

My setup is very similar to yours:

  • host (
    • sslh:443
    • ssh:2222
    • docker
      • container1 (container:443 -> host:4443)
      • container2 (container:443 -> host:1194)

Using the documentation of sslh allowed me to successfully use transparent mode to ssh:443. So aparently, routing to host processes in transparent mode works.

Here is my current set of (host) rules

iptables -t mangle -N SSLH
iptables -t mangle -A PREROUTING -p tcp -m socket --transparent -j SSLH
#The next line routes ssh@host via sslh back to the client. Add more rules like this for other services running on the host (with their respective port)
iptables -t mangle -A OUTPUT -p tcp -m tcp --sport 2222 -j SSLH
iptables -t mangle -A SSLH -j MARK --set-mark 0x1 
iptables -t mangle -A SSLH -j ACCEPT
ip rule add fwmark 0x1 lookup 100 
ip route add local dev lo table 100

This route ssh traffic transparently between host (port 2222) and client via sshl. But adding a similar rule for docker containers however did not work for me (probably due dockers own extensive iptables rules/networking)

My solution now goes like this: Assign a fixed (container internal) ip to the docker containers and let sslh access the containers via this ip and the internal port of that container instead of via or

  • container1:
  • container2:

My /etc/default/sslh file has this entry now

DAEMON_OPTS="-n --user sslh --transparent --timeout 5 --listen --ssh --ssl --openvpn --pidfile /var/run/sslh/sslh.pid"

In order to assign a fixed ip for a container, you just create a new network e.g. via cli or via docker-compose.

Since I am using docker-compose, I did it like this:

version: '2.4'
    image: nginx
      - "80:80"
      - "4443:443"
      default: null

        - subnet:

That solution will add the container "nginx" to two networks: First of all the new "static-network" with a fixed ip and also to the default network (convenient if you want to e.g. link two containers within the same docker-compose file via the autogenerated default network).

I have to admit though, that this solution is only a workaround. I simply do not understand the magic of routing and iptables in that depth, that I just can write better routing rules. Yet, this workaround does the job for me: Transparent sslh proxying to host and docker containers.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.