{
  "id": 12022157,
  "title": "Self-hosted HTTP tunnels with SSH and Nginx",
  "url": "https://urgent.news/2026/10/04/self-hosted-http-tunnels-with-ssh-and-nginx-12022157",
  "topic": "tech",
  "section": "Tech",
  "published": "2026-10-04T22:25:10.000Z",
  "source": {
    "name": "Hacker News",
    "slug": "hacker-news",
    "url": "https://vincent.bernat.ch/en/blog/2026-http-over-ssh"
  },
  "original_language": "en",
  "account": "A friend requests assistance in proofreading your blog post about self-hosted HTTP tunnels using SSH and Nginx. Several options exist to achieve this, including commercial services such as ngrok and Cloudflare Quick Tunnels, as well as self-hostable solutions like frp and localtunnel. However, they typically require a specific client and rely on a particular SSH server, such as sish.\n\nIn this guide, we'll implement a self-hosted solution using solely OpenSSH and nginx. The process begins by forwarding connections from a remote server port to your local service. When specifying 0 as the remote port, the server assigns a free port. Subsequently, Nginx is configured to proxy requests from https://p41535.ssh.luffy.cx to http://127.0.0.1:41535.\n\nTo obtain a wildcard certificate through Let’s Encrypt, we need to add DNS records for *.ssh.luffy.cx. If you're using Route 53 as your DNS provider, you can manage the ACME DNS-01 challenges for both wildcard certificates and domains served by multiple web servers. In this scenario, NixOS automatically obtains the certificates.\n\nThe port is the only confidential element in this setup, preventing unauthorized access. Other forwarding solutions may add a random string to the domain name to hinder enumeration of possible values. However, using ngx_http_secure_link_module allows for enhanced security. This module computes a hash based on a set of values, including a secret, and compares it with the hash provided in the request. Since the hash is base64-encoded and domain names are case-insensitive, the hash is added as a username within the URL, along with its expiration timestamp.\n\nThe client sends the username via HTTP basic authentication to the server, which can be used with most HTTP clients, including curl. Nginx retrieves the username through the $remote_user variable. A map directive is utilized to extract the hash and expiration timestamp from $remote_user and rejoin them with a comma. The module also requires the string to hash, which includes the expiration timestamp, port, and a secret.\n\nThe module returns the status of the check in the $secure_link variable. If the hash is incorrect or missing, a 401 error with a WWW-Authenticate header is returned, prompting the client for credentials. If the link has expired, a 410 error is returned instead. Before forwarding the request, the Authorization header is removed, and a few directives are added to support WebSocket connections.\n\nThe complete configuration is provided, allowing you to generate a self-hosted tunnel and share a URL with others. To accomplish this, you only need OpenSSH and nginx, both already running on the server. A single command is required to create the tunnel and generate the URL.\n\nIf you're using NixOS, consider checking out the provided http-over-ssh.nix configuration.",
  "summary": null,
  "key_points": [],
  "editors_take": null,
  "illustration": null,
  "coverage": {
    "outlets": 2,
    "also_reported_by": [
      {
        "outlet": "Lobsters",
        "title": "Self-hosted HTTP tunnels with SSH and nginx",
        "url": "https://urgent.news/2026/10/04/self-hosted-http-tunnels-with-ssh-and-nginx",
        "published": "2026-10-04T19:08:58.000Z"
      }
    ]
  },
  "ai_generated": true,
  "disclaimer": "Summaries, key points and the editor’s take are written by software from other outlets’ reporting and may contain errors — always check the linked original."
}