{
  "version": "v1",
  "slug": "host-a-tor-hidden-service",
  "title": "How to host a service as a Tor hidden service",
  "description": "Set up a .onion address for your website, wiki, or app — torrc config, hostname rotation, vanity addresses, and the operational pitfalls (clock skew, leaking real-IP via headers, descriptor uptime) that quietly de-anonymize hidden services.",
  "intro": "Running a Tor hidden service is operationally close to running a normal web service, with one critical difference: every leak is permanent. Below: the minimum-viable setup, the v3-onion-only world we live in, and the failure modes that ruin the anonymity you're paying for.",
  "body_plain": "Why host on Tor Hide the server's location. No DNS, no public IP, no AS-level attribution from a casual observer. End-to-end encryption by default — the .onion handshake replaces TLS-via-CA. (You can still layer TLS on top for compatibility, but it's not required.) Censorship resistance. Hidden services are reachable from anywhere the Tor network is reachable; no domain to seize, no IP to block at the country level. Caveat: hiding the server's location does not hide what the service does. Logs you keep, accounts you accept, payment processors you forward to — all unchanged. v3 onion only, in 2026 v2 onions (short 16-char) were deprecated in 2021. Use only v3 (56-char) hidden services. They use better crypto, support client auth, and have no known mass-deanon attacks. If a tutorial references v2 it's out of date. Minimum-viable setup Install Tor on the same host as the service. apt install tor on Debian/Ubuntu, equivalent elsewhere. Edit /etc/tor/torrc : HiddenServiceDir /var/lib/tor/my-service/ HiddenServicePort 80 127.0.0.1:8080 HiddenServiceVersion 3 (Port 80 inside the hidden service maps to whatever your local app is listening on.) Restart Tor. systemctl restart tor . Read the hostname. cat /var/lib/tor/my-service/hostname — that's your .onion address. Test. Open Tor Browser → paste the .onion. Page loads = working. Page doesn't load = check Tor logs ( /var/log/tor/log ). Operational pitfalls (the ones that bite) Clock skew. If your server's clock drifts by more than a few minutes, your descriptor won't post and the .onion goes dark. Run chrony or systemd-timesyncd . Leaking real-IP via headers. Default nginx config logs the request and includes X-Forwarded-For or X-Real-IP based on the proxy. If anything in your stack writes the request's real IP into the page (an error message, a debug header, a chat that echoes back), you've defeated the .onion's purpose. Audit every layer. Public referer leak. Outbound links from the hidden service that go to clearnet send the .onion as Referer by default. Mitigation: Referrer-Policy: no-referrer header. Server-side tooling. Hidden service operator wallets / databases / cron jobs that phone home outside Tor reveal the real IP. Run all egress through Tor or block egress entirely on the firewall. SSH backdoor on the real-IP interface. Convenient for admin, terrible for anonymity. Either run SSH on a separate hidden service too, or disable password auth + key-only + non-default port + fail2ban + accept the residual leak. Descriptor downtime. If the Tor daemon goes down for hours, the hidden service descriptor expires + you have to wait for re-propagation. Keep systemd auto-restart on the Tor unit. Vanity onion addresses If you want your .onion to start with specific characters (e.g. \"xmrclub...\"), use mkp224o . Eight-character prefix takes hours on a laptop; longer prefixes are exponentially harder. Keep the generation host offline / isolated — anyone who sees the resulting private key ( hs_ed25519_secret_key ) controls the address. Onion-Location header (advertise to clearnet visitors) If you also serve a clearnet version, add the Onion-Location response header so Tor Browser shows a \".onion available\" banner. Modern Tor Browser auto-redirects to it on first visit: Onion-Location: http://<your-56-char-v3-onion>.onion$request_uri The xmr.club onion audit daily-probes every operator-published Onion-Location header against the .onion we list, so an inconsistency between the header and the address you publish elsewhere shows up immediately. Client authorization (advanced) If you don't want the hidden service to be publicly browsable, use v3 client-auth. The server requires a per-client public key in its config; clients without the matching key get a \"permission denied\" from Tor itself, not your app. Useful for private-team services, customer-only support portals, etc. Hosting that already handles Tor for you If you don't want to run Tor on your VPS yourself — picks below run no-KYC providers that either pre-configure Tor on a managed instance or accept your manually-installed setup. Pay in XMR; treat the host as a soft adversary anyway.",
  "body_html": "\n      <section>\n        <h2 class=\"section-h\">Why host on Tor</h2>\n        <ul class=\"bullet-list\">\n          <li><strong>Hide the server's location.</strong> No DNS, no public IP, no AS-level attribution from a casual observer.</li>\n          <li><strong>End-to-end encryption</strong> by default — the .onion handshake replaces TLS-via-CA. (You can still layer TLS on top for compatibility, but it's not required.)</li>\n          <li><strong>Censorship resistance.</strong> Hidden services are reachable from anywhere the Tor network is reachable; no domain to seize, no IP to block at the country level.</li>\n          <li><strong>Caveat:</strong> hiding the server's location does not hide what the service does. Logs you keep, accounts you accept, payment processors you forward to — all unchanged.</li>\n        </ul>\n      </section>\n\n      <section>\n        <h2 class=\"section-h\">v3 onion only, in 2026</h2>\n        <p>v2 onions (short 16-char) were deprecated in 2021. Use only v3 (56-char) hidden services. They use better crypto, support client auth, and have no known mass-deanon attacks. If a tutorial references v2 it's out of date.</p>\n      </section>\n\n      <section>\n        <h2 class=\"section-h\">Minimum-viable setup</h2>\n        <ol class=\"bullet-list\">\n          <li><strong>Install Tor</strong> on the same host as the service. <code>apt install tor</code> on Debian/Ubuntu, equivalent elsewhere.</li>\n          <li><strong>Edit <code>/etc/tor/torrc</code></strong>:\n            <pre><code>HiddenServiceDir /var/lib/tor/my-service/\nHiddenServicePort 80 127.0.0.1:8080\nHiddenServiceVersion 3</code></pre>\n            (Port 80 inside the hidden service maps to whatever your local app is listening on.)</li>\n          <li><strong>Restart Tor.</strong> <code>systemctl restart tor</code>.</li>\n          <li><strong>Read the hostname.</strong> <code>cat /var/lib/tor/my-service/hostname</code> — that's your .onion address.</li>\n          <li><strong>Test.</strong> Open Tor Browser → paste the .onion. Page loads = working. Page doesn't load = check Tor logs (<code>/var/log/tor/log</code>).</li>\n        </ol>\n      </section>\n\n      <section>\n        <h2 class=\"section-h\">Operational pitfalls (the ones that bite)</h2>\n        <ol class=\"bullet-list\">\n          <li><strong>Clock skew.</strong> If your server's clock drifts by more than a few minutes, your descriptor won't post and the .onion goes dark. Run <code>chrony</code> or <code>systemd-timesyncd</code>.</li>\n          <li><strong>Leaking real-IP via headers.</strong> Default nginx config logs the request and includes <code>X-Forwarded-For</code> or <code>X-Real-IP</code> based on the proxy. If anything in your stack writes the request's real IP into the page (an error message, a debug header, a chat that echoes back), you've defeated the .onion's purpose. Audit every layer.</li>\n          <li><strong>Public referer leak.</strong> Outbound links from the hidden service that go to clearnet send the .onion as Referer by default. Mitigation: <code>Referrer-Policy: no-referrer</code> header.</li>\n          <li><strong>Server-side tooling.</strong> Hidden service operator wallets / databases / cron jobs that phone home outside Tor reveal the real IP. Run all egress through Tor or block egress entirely on the firewall.</li>\n          <li><strong>SSH backdoor on the real-IP interface.</strong> Convenient for admin, terrible for anonymity. Either run SSH on a separate hidden service too, or disable password auth + key-only + non-default port + fail2ban + accept the residual leak.</li>\n          <li><strong>Descriptor downtime.</strong> If the Tor daemon goes down for hours, the hidden service descriptor expires + you have to wait for re-propagation. Keep <code>systemd</code> auto-restart on the Tor unit.</li>\n        </ol>\n      </section>\n\n      <section>\n        <h2 class=\"section-h\">Vanity onion addresses</h2>\n        <p>If you want your .onion to start with specific characters (e.g. \"xmrclub...\"), use <a href=\"https://github.com/cathugger/mkp224o\"><code>mkp224o</code></a>. Eight-character prefix takes hours on a laptop; longer prefixes are exponentially harder. <strong>Keep the generation host offline / isolated</strong> — anyone who sees the resulting private key (<code>hs_ed25519_secret_key</code>) controls the address.</p>\n      </section>\n\n      <section>\n        <h2 class=\"section-h\">Onion-Location header (advertise to clearnet visitors)</h2>\n        <p>If you also serve a clearnet version, add the <code>Onion-Location</code> response header so Tor Browser shows a \".onion available\" banner. Modern Tor Browser auto-redirects to it on first visit:</p>\n        <pre><code>Onion-Location: http://&lt;your-56-char-v3-onion&gt;.onion$request_uri</code></pre>\n        <p>The xmr.club <a href=\"/onion-audit\">onion audit</a> daily-probes every operator-published Onion-Location header against the .onion we list, so an inconsistency between the header and the address you publish elsewhere shows up immediately.</p>\n      </section>\n\n      <section>\n        <h2 class=\"section-h\">Client authorization (advanced)</h2>\n        <p>If you don't want the hidden service to be publicly browsable, use v3 client-auth. The server requires a per-client public key in its config; clients without the matching key get a \"permission denied\" from Tor itself, not your app. Useful for private-team services, customer-only support portals, etc.</p>\n      </section>\n\n      <section>\n        <h2 class=\"section-h\">Hosting that already handles Tor for you</h2>\n        <p>If you don't want to run Tor on your VPS yourself — picks below run no-KYC providers that either pre-configure Tor on a managed instance or accept your manually-installed setup. Pay in XMR; treat the host as a soft adversary anyway.</p>\n      </section>\n    ",
  "picks": [
    {
      "category": "hosting",
      "id": "njalla-vps",
      "name": "Njalla VPS",
      "url": "https://www.xmr.club/hosting/njalla-vps",
      "markdown_twin": "https://www.xmr.club/llm/hosting/njalla-vps.txt",
      "why": "Privacy-respecting VPS, XMR payment, anonymous-friendly support — install Tor yourself, no questions asked."
    },
    {
      "category": "hosting",
      "id": "1984-hosting",
      "name": "1984 Hosting",
      "url": "https://www.xmr.club/hosting/1984-hosting",
      "markdown_twin": "https://www.xmr.club/llm/hosting/1984-hosting.txt",
      "why": "Iceland-based, free-speech focused. Accepts crypto. Hands-off on what you host."
    },
    {
      "category": "hosting",
      "id": "incognet",
      "name": "Incognet",
      "url": "https://www.xmr.club/hosting/incognet",
      "markdown_twin": "https://www.xmr.club/llm/hosting/incognet.txt",
      "why": "No-KYC hosting + email signup. Tor mirror."
    },
    {
      "category": "tools",
      "id": "tor-browser",
      "name": "Tor Browser",
      "url": "https://www.xmr.club/tools/tor-browser",
      "markdown_twin": "https://www.xmr.club/llm/tools/tor-browser.txt",
      "why": "Test your hidden service from the same UA your users will. Mandatory."
    }
  ],
  "url": "https://www.xmr.club/guides/host-a-tor-hidden-service",
  "markdown_twin": "https://www.xmr.club/llm/guides/host-a-tor-hidden-service.txt"
}