How To Host a Tor Onion Service on Linux With nginx and ASP.NET Core



Last updated: October 28th, 2026

This tutorial is for educational purposes. Make sure using Tor is legal where you live.

An onion service gives your website a .onion address that Tor Browser users can reach without either side revealing its IP address. It needs no domain, no certificate and no open inbound ports. This guide sets one up on a Linux server (Ubuntu or Debian) in front of an existing site, using nginx and an ASP.NET Core app as the example. If your site runs on IIS, see the Windows Server version of this guide.

Overview

Tor Browser --(Tor network)--> tor on your server --> nginx 127.0.0.1:8080 --> Kestrel 127.0.0.1:5000

Tor receives onion connections and forwards them to a local port. Everything listens on 127.0.0.1, so nothing new is exposed to the internet.

Step 1: Install Tor From the Official Repository

Distribution packages are often an older Tor release. Old releases eventually fall out of step with the network and onion services can become unreachable, so use the Tor Project's own repository:

# Debian/Ubuntu: install Tor from the Tor Project's repository (distro packages can lag behind)
sudo apt install -y apt-transport-https gpg lsb-release
wget -qO- https://deb.torproject.org/torproject.org/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc \
  | gpg --dearmor | sudo tee /usr/share/keyrings/deb.torproject.org-keyring.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/deb.torproject.org-keyring.gpg] https://deb.torproject.org/torproject.org $(lsb_release -cs) main" \
  | sudo tee /etc/apt/sources.list.d/tor.list
sudo apt update
sudo apt install -y tor deb.torproject.org-keyring
tor --version

Step 2: Configure the Onion Service in torrc

# /etc/tor/torrc  (add at the end)
HiddenServiceDir /var/lib/tor/mysite/
HiddenServicePort 80 127.0.0.1:8080
  • HiddenServiceDir: where Tor stores the keys and the hostname file. Tor creates it with the right owner (debian-tor) and permissions.
  • HiddenServicePort 80 127.0.0.1:8080: visitors connect to port 80 on the onion address; Tor forwards them to port 8080 locally.
  • For more sites, add more HiddenServiceDir / HiddenServicePort pairs.
sudo systemctl restart tor
sudo systemctl status tor --no-pager
sudo cat /var/lib/tor/mysite/hostname     # your new .onion address

The hostname file contains your address: 56 characters followed by .onion.

Step 3: Add an nginx Server Block for the Onion Address

You could point HiddenServicePort straight at Kestrel, but nginx in between lets you serve the onion site with its own settings (no access logs, no version headers, caching or limits if you need them):

# /etc/nginx/sites-available/mysite-onion
server {
    listen 127.0.0.1:8080;                 # only Tor can reach this
    server_name youronionaddressxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.onion;

    server_tokens off;                     # no nginx version in headers/error pages
    access_log off;                        # every visitor is 127.0.0.1 anyway

    location / {
        proxy_pass         http://127.0.0.1:5000;   # Kestrel
        proxy_http_version 1.1;
        proxy_set_header   Host              $host;
        proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_set_header   Upgrade           $http_upgrade;
        proxy_set_header   Connection        "upgrade";
    }
}
sudo ln -s /etc/nginx/sites-available/mysite-onion /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

# Test through Tor from the server itself (Tor's SOCKS proxy listens on 9050 by default)
curl -sI --socks5-hostname 127.0.0.1:9050 http://youronionaddressxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.onion/

A 200 (or your app's normal status) from the curl test means the whole chain works. Then open the address in Tor Browser from another machine.

Step 4: Make ASP.NET Core Work on Both Addresses

One app can serve both the clearnet domain and the onion address. Two things matter: trust nginx's forwarded headers, and don't redirect onion visitors to HTTPS. Tor Browser connects to onion addresses over plain http (Tor already encrypts the connection end to end), so an HTTPS redirect sends them to a URL that doesn't work:

// Program.cs: one ASP.NET Core app behind nginx, serving both example.com and the .onion
using Microsoft.AspNetCore.HttpOverrides;

var builder = WebApplication.CreateBuilder(args);

builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
    // nginx runs on the same machine; loopback proxies are trusted by default.
});

var app = builder.Build();

app.UseForwardedHeaders();

static bool IsOnion(HttpContext ctx) =>
    ctx.Request.Host.Host.EndsWith(".onion", StringComparison.OrdinalIgnoreCase);

// Onion visitors use plain http (Tor encrypts end to end): never redirect them to https.
app.UseWhen(ctx => !IsOnion(ctx), clearnet => clearnet.UseHttpsRedirection());

app.MapGet("/", (HttpContext ctx) => IsOnion(ctx) ? "Hello, Tor" : "Hello, clearnet");
app.Run();

Also remember that every onion request arrives from 127.0.0.1. Per-IP rate limits, bans or "one vote per IP" logic will treat all Tor users as a single visitor; key those features on something else for onion traffic.

Step 5: Advertise the Onion Address

Tor Browser shows a ".onion available" button when an HTTPS page sends an Onion-Location header. Add it to your clearnet site's nginx config:

# In your existing HTTPS (port 443) server block for example.com:
add_header Onion-Location http://youronionaddressxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.onion$request_uri always;

Make the Onion Site Tor-Friendly

  • Self-host CSS, fonts and scripts. Assets from CDNs cause clearnet requests from the visitor's browser.
  • Work without JavaScript. Many Tor users run Tor Browser's "Safest" level, which disables it.
  • Use relative links (/about, not https://example.com/about) so visitors stay on the onion address.
  • Don't leak the server's IP in headers, error pages or email links. server_tokens off is a start.

Back Up the Onion Keys

The onion address is the key pair in HiddenServiceDir. Lose hs_ed25519_secret_key and the address is gone for good; leak it and someone else can impersonate your site.

# Back up the onion identity (the secret key IS the address). Store it offline and encrypted.
sudo tar -czf ~/mysite-onion-keys.tar.gz -C /var/lib/tor mysite

# Restore on a new server BEFORE starting Tor there:
sudo tar -xzf mysite-onion-keys.tar.gz -C /var/lib/tor
sudo chown -R debian-tor:debian-tor /var/lib/tor/mysite
sudo chmod 700 /var/lib/tor/mysite
sudo systemctl restart tor

Optional: A Vanity Address

Tools such as mkp224o generate keys until the address starts with a prefix you choose (for example mysite...onion). Each extra character takes roughly 32 times longer to find; 5 to 7 characters is practical on a normal machine. Copy the generated key files into HiddenServiceDir and restart Tor.

Troubleshooting

  • Tor won't start: sudo journalctl -u tor@default -e shows the error, usually a typo in torrc or wrong permissions on HiddenServiceDir (it must be owned by debian-tor with mode 700).
  • Onion address times out: check that Tor is a current release (tor --version), that it has bootstrapped to 100%, and that the local port in HiddenServicePort is the one nginx listens on.
  • Redirect loop or "unable to connect": your app or nginx is redirecting onion requests to HTTPS or to the clearnet domain.

Frequently Asked Questions

How do I make a .onion website?

Install Tor on a server that runs your website, add HiddenServiceDir and HiddenServicePort lines to /etc/tor/torrc, restart Tor, and read your address from the hostname file in that directory.

Where is torrc on Linux?

For the system Tor service on Debian and Ubuntu it is /etc/tor/torrc. The onion service keys live under /var/lib/tor/.

Do I need HTTPS for a .onion site?

No. Connections to onion services are end-to-end encrypted and authenticated by the address itself. Serve plain http on the onion address and keep HTTPS for the clearnet domain.

Can I run the same site on clearnet and Tor?

Yes. Point both at the same app, skip the HTTPS redirect for the onion host, use relative links, and add an Onion-Location header on the clearnet site.

Related: Tor onion service on Windows Server with IIS and accepting Monero payments with monero-wallet-rpc.


Comments

No Comments

CAPTCHA

Tagged: ASP.NET Core


Join my email list!



ryan
About Me

With over 15 years in tech, I've excelled as a senior software engineer, specializing in ASP.NET, C#, SQL, Azure, and front-end technologies. I've led diverse projects across various sectors, from startups to global corporations, particularly during my decade in the San Francisco Bay Area.


Sign Up Hostinger Email