{"id":13552686,"url":"https://github.com/tamcore/ssh-punchhole","last_synced_at":"2026-02-15T20:05:43.164Z","repository":{"id":38383439,"uuid":"477287514","full_name":"tamcore/ssh-punchhole","owner":"tamcore","description":"Tiny SSH based reverse-tunnel to expose services behind a firewall","archived":false,"fork":false,"pushed_at":"2025-03-07T18:26:10.000Z","size":49,"stargazers_count":54,"open_issues_count":1,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-27T05:24:26.777Z","etag":null,"topics":["proxy","reverse-proxy","tunnel"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tamcore.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-04-03T08:59:26.000Z","updated_at":"2025-03-17T04:07:03.000Z","dependencies_parsed_at":"2023-12-02T07:22:46.853Z","dependency_job_id":"b9a91a96-7662-4627-9495-6c2c0c6738c6","html_url":"https://github.com/tamcore/ssh-punchhole","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tamcore%2Fssh-punchhole","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tamcore%2Fssh-punchhole/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tamcore%2Fssh-punchhole/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tamcore%2Fssh-punchhole/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tamcore","download_url":"https://codeload.github.com/tamcore/ssh-punchhole/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248725500,"owners_count":21151717,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["proxy","reverse-proxy","tunnel"],"created_at":"2024-08-01T12:02:07.849Z","updated_at":"2026-02-15T20:05:38.132Z","avatar_url":"https://github.com/tamcore.png","language":"Shell","funding_links":[],"categories":["Shell"],"sub_categories":[],"readme":"# ssh-punchhole\n\nThis is my simple way of exposing my self-hosted services without having to deal with port-forwarding and exposing my home IP to the public.\n\nTechnically it only requires a cheap VPS for the public endpoint and could forward the traffic directly to your service (i.e. Nextcloud). But having a local reverse proxy like Traefik is recommended.\n\nThis container will be the connecting part between the both endpoints. It'll establish a SSH session to the VPS, open (public) ports there and forward the traffic arriving at those ports to the local destination.\n\nIf you fancy, you can have it bind directly to :80 and :443 (which would require it to connect as root, as everything bellow 1024 is restricted), but then you'll lose the ability to log the Source IP. So I'd recommend having a reverse proxy running on your VPS as well and have it configured to forward incoming (public) traffic to your local (tunnel) ports. If you want to bind to a non-loopback IP (i.e. 0.0.0.0 or your servers public IP), you have to enable the `GatewayPorts` in your server's `sshd_config`. Additionally, if the port is bellow 1024, you'll have to connect as root.\n\n## Quickstart with haproxy on the VPS, docker-compose for your this tunneling container and Traefik as reverse Proxy\n\n1. Create the known_hosts file for HostKeyVerification\n````\nssh-keyscan ${IP_OF_YOUR_VPS} \u003e known_hosts\n````\n2. Create an id_rsa keypair (don't set a passphrase!)\n````\nssh-keygen -f id_rsa\n````\n3. Create your docker-compose.yaml similar to this\n - Instead of using relative paths for your volumes you might want to use absolute paths\n - This will have the ssh-punchhole container connect as root on your VPS\n    - and open both port 980 and 9443 on localhost (127.0.0.1)\n    - and forward their traffic to traefik:80 and traefik:443\n    - **Note:** If you, for example, would want to have both 980 and 9443 forwarded to the same destination, just omit the last destination. The script will take notice and use the first (and only) provided destination.\n    - In this example, my traefik bridge network is called traefik-net, you might have to adjust that to your specific setup. Also, my instance of Traefik is deployed with the name \"traefik\". You might want to adjust that as well.\n````\nversion: \"3\"\n\nservices:\n  ssh-punchhole:\n    container_name: ssh-punchhole\n    image: ghcr.io/tamcore/ssh-punchhole:v1\n    volumes:\n      - './id_rsa:/id_rsa:ro'\n      - './known_hosts:/known_hosts:ro'\n    restart: unless-stopped\n    environment:\n      - SSH_PORT=22                             # Optional: Change if you have your SSHd running on a non-standard port\n      - SSH_USER=root                           # Optional: Change, if you want to login as a non-root user\n      - REMOTE_HOST=${IP_OF_YOUR_VPS}           # Required: Hostname of your VPS\n      # And this will forward 127.0.0.1:980 on the VPS to traefik:80 and :9443 to traefik:443\n      - \"REMOTE_FORWARD=127.0.0.1:980 127.0.0.1:9443\"\n      - \"LOCAL_DESTINATION=traefik:80 traefik:443\"\n    networks:\n      - traefik-net\n\nnetworks:\n  traefik-net:\n    external: true\n````\n4. To tell Traefik to trust the proxyProtocol headers it receives through the SSH-Tunnel, you'll have to add 10.0.0.0/16 as proxyProtocol.trustedIps for your endPoints. For my setup, the traefik.yaml contains the following\n````\nentryPoints:\n  http:\n    address: \":80\"\n    proxyProtocol:\n      trustedIPs:\n        - \"10.0.0.0/16\"\n  https:\n    address: \":443\"\n    proxyProtocol:\n      trustedIPs:\n        - \"10.0.0.0/16\"\n````\n5. Now, all that's left, is to install haproxy on your VPS (something alongside *{apt,dnf,yum} install haproxy* should do the trick) with an /etc/haproxy/haproxy.cfg configuration similar to\n  - This will publicly open ports 80 and 443 on all available IP addresses provisioned on the VPS and forward the traffic as follows\n    - $ENDUSER -\u003e 0.0.0.0:80  (aka HTTP)  -\u003e localhost:980  -\u003e \\\u003cthrough your tunnel\\\u003e -\u003e traefik:80  -\u003e $WHATEVER_SERVICE\n    - $ENDUSER -\u003e 0.0.0.0:443 (aka HTTPS) -\u003e localhost:9443 -\u003e \\\u003cthrough your tunnel\\\u003e -\u003e traefik:443 -\u003e $WHATEVER_SERVICE\n````\nglobal\n   maxconn 4096\n\ndefaults\n   log   global\n   mode   http\n   retries   3\n   option redispatch\n   maxconn   2000\n   timeout connect 5000\n   timeout client  50000\n   timeout server  50000\n\nfrontend http\n    bind 0.0.0.0:80\n    mode tcp\n    default_backend backend-http\n\nfrontend https\n    bind 0.0.0.0:443\n    mode tcp\n    default_backend backend-https\n\nbackend backend-http\n    mode tcp\n    server localhost 127.0.0.1:980 send-proxy-v2\n\nbackend backend-https\n    mode tcp\n    server localhost 127.0.0.1:9443 send-proxy-v2\n````\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftamcore%2Fssh-punchhole","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftamcore%2Fssh-punchhole","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftamcore%2Fssh-punchhole/lists"}