Routing the SSH port for GitLab-ce with docker swarm and using traefik

I set up Traefik and GitLab in docker swarm. They work, except I can't clone from GitLab through SSH. I saw several discussions about this but I can't get mine to work. Would someone be able to review my config please?

Notes:
The server's sshd uses port 2222
I use port 22 for my gitlab
cloning through HTTPS works

Error:
Cloning into 'demo-project'...
ssh: connect to host gitlab.example.com port 22: Operation timed out
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

My config files:

Traefik stack:

volumes:
  letsencrypt:
  logs:

networks:
  traefik-public:
    external: true

services:
  traefik:
    image: traefik:v2.11.3
    deploy:
      placement:
        constraints:
          - node.role == manager
      replicas: 1
      restart_policy:
        condition: on-failure
        delay: 10s
        max_attempts: 5
        window: 30s
      labels:
        # Enable Traefik for this service, to make it available in the public network
        - traefik.enable=true
        # Use the traefik-public network (declared below)
        - traefik.docker.network=traefik-public
        # admin-auth middleware with HTTP Basic auth
        # Using the environment variables USERNAME and HASHED_PASSWORD
        - "traefik.http.middlewares.dashboard-auth.basicauth.users=${USERNAME?Variable not set}:${HASHED_PASSWORD?Variable not set}"
        # https-redirect middleware to redirect HTTP to HTTPS
        # It can be re-used by other stacks in other Docker Compose files
        - traefik.http.middlewares.https-redirect.redirectscheme.scheme=https
        - traefik.http.middlewares.https-redirect.redirectscheme.permanent=true
        - traefik.http.routers.dashboard.entrypoints=web
        - traefik.http.routers.dashboard.rule=Host(`proxy.example.com`)
        - traefik.http.routers.dashboard.middlewares=https-redirect
        # traefik-https the actual router using HTTPS
        - traefik.http.routers.dashboard-secure.rule=Host(`proxy.example.com`)
        - traefik.http.routers.dashboard-secure.entrypoints=websecure
        - traefik.http.routers.dashboard-secure.tls=true
        - traefik.http.routers.dashboard-secure.service=api@internal
        - traefik.http.routers.dashboard-secure.tls.certresolver=letsencrypt
        - traefik.http.routers.dashboard-secure.middlewares=dashboard-auth
        # dummy service fro swarm port detection.
        - traefik.http.services.dashboard.loadbalancer.server.port=8080
    ports:
      - target: 80
        published: 80
        mode: host
      - target: 443
        published: 443
        mode: host
      - target: 22
        published: 22
        protocol: tcp
        mode: host
    volumes:
      # certs
      - ./letsencrypt:/letsencrypt
      # logs
      - ./logs:/logs
      # security todo
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - traefik-public
    command:
      # Enable Docker in Traefik, so that it reads labels from Docker services
      - --providers.docker
      # Do not expose all Docker services, only the ones explicitly exposed
      - --providers.docker.exposedbydefault=false
      # Enable Docker Swarm mode
      - --providers.docker.swarmmode
      # set socket
      # - --providers.docker.endpoint=tcp://dockerproxy:2375
      # set default network
      - --providers.docker.network=traefik-public
      # Enable the access log, with HTTP requests
      - --accesslog.filepath=/logs/access.log
      # set log to debug level
      - --log.level=DEBUG
      # Enable the Traefik log, for configurations and errors
      - --log.filepath=/logs/main.log
      # Activate dashboard
      - --api
      # disable
      - --serverstransport.insecureskipverify=true
      # Set up LetsEncrypt certificate resolver
      # staging environment of LE, remove for real certs
      - --certificatesresolvers.letsencrypt.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
      - --certificatesresolvers.letsencrypt.acme.dnschallenge=true
      - --certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare
      - --certificatesResolvers.letsencrypt.acme.dnschallenge.delayBeforeCheck=20
      - --certificatesResolvers.letsencrypt.acme.dnschallenge.resolvers=1.1.1.1:53,8.8.8.8:53
      - --certificatesResolvers.letsencrypt.acme.dnschallenge.disablepropagationcheck=true
      - --certificatesresolvers.letsencrypt.acme.email=mymail@gmail.com
      - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
      - --certificatesresolvers.letsencrypt.acme.tlschallenge=true
      # Set up for Gitlab SSH
      - --entrypoints.gitlab-ssh.address=:22
      # Set up an insecure listener that redirects all traffic to TLS
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      - --entrypoints.web.http.redirections.entrypoint.to=websecure
      - --entrypoints.web.http.redirections.entrypoint.scheme=https
      # Set up the TLS configuration for our websecure listener
      - --entrypoints.websecure.http.tls=true
      - --entrypoints.websecure.http.tls.certResolver=letsencrypt
      - --entrypoints.websecure.http.tls.domains[0].main=example.com
      - --entrypoints.websecure.http.tls.domains[0].sans=*.example.com
    environment:
      - "CF_DNS_API_TOKEN=my-cloudflare-dns-token"
      - "CF_API_EMAIL=mymail@gmail.com"

Gitlab-ce stack:

version: '3.8'

services:
  gitlab:
    image: gitlab/gitlab-ce:16.8.5-ce.0
    # ports:
    #   - target: 22
    #     published: 22
    #     protocol: tcp
    #     mode: host
    hostname: "gitlab.example.com"
    environment:
      GITLAB_OMNIBUS_CONFIG: "from_file('/etc/gitlab/gitlab.rb')"
    configs:
      - source: "gitlab_rb"
        target: "/etc/gitlab/gitlab.rb"
    secrets:
      - gitlab_root_password
    volumes:
      - gitlab_config:/etc/gitlab
      - gitlab_logs:/var/log/gitlab
      - gitlab_data:/var/opt/gitlab
    healthcheck:
      interval: 3m
    networks:
      - gitlab-network
      - traefik-public
    deploy:
      # replicas: 1
      update_config:
        parallelism: 1
        delay: 10s
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 1
      placement:
        constraints:
          # - node.role == manager
          - node.hostname == worker3-centos-hel1-1
      labels:
        - "traefik.enable=true"
        - "traefik.docker.network=traefik-public"
        - "traefik.http.routers.gitlab.rule=Host(`gitlab.example.com`)"
        - "traefik.http.routers.gitlab.entrypoints=websecure"
        - "traefik.http.routers.gitlab.service=gitlab"
        - "traefik.http.routers.gitlab.tls.certresolver=letsencrypt"
        - "traefik.http.services.gitlab.loadbalancer.server.port=80"
        # define the ssh entrypoint
        # - "traefik.tcp.routers.gitlab-ssh.tls=true"
        - "traefik.tcp.routers.gitlab-ssh.entrypoints=gitlab-ssh"
        # define hostname for the gitlab-ssh router
        - "traefik.tcp.routers.gitlab-ssh.rule=HostSNI(`*`)"
        # define service to use
        - "traefik.tcp.routers.gitlab-ssh.service=gitlab-ssh-service"
        # define backend port to use, this is the port Gitlab ssh listens on
        - "traefik.tcp.services.gitlab-ssh-service.loadbalancer.server.port=22"

configs:
  gitlab_rb:
    file: "./gitlab/gitlab.rb"

secrets:
  gitlab_root_password:
    external: true

volumes:
  gitlab_config:
  gitlab_logs:
  gitlab_data:

networks:
  gitlab-network:
    external: true
  traefik-public:
    external: true

gitlab.rb configuration file:

external_url 'https://gitlab.example.com'

# Set the initial root password
gitlab_rails['initial_root_password'] = File.read('/run/secrets/gitlab_root_password').gsub("\n", "")
gitlab_rails['gitlab_ssh_host'] = 'gitlab.example.com
gitlab_rails['gitlab_shell_ssh_port'] = 22

# Configure Nginx to listen on HTTP and trust headers for SSL termination
nginx['listen_port'] = 80
nginx['listen_https'] = false
nginx['proxy_set_headers'] = {
"Host" => "$http_host",
"X-Real-IP" => "$remote_addr",
"X-Forwarded-For" => "$proxy_add_x_forwarded_for",
"X-Forwarded-Proto" => "https",
"X-Forwarded-Ssl" => "on"
}

On first sight the Traefik config looks ok, maybe try to run a Linux server instead to see if ssh works. That way you can determine if it’s an issue with Traefik or the target service.