Tor Onion Service in a Reverse Proxy Environment

This article describes a possible implementation design of a Tor Onion Service on an already existing web service behind an HTTPS reverse proxy. The goal is to have the same reverse proxied site available over the regular web as well as over the Tor network but without any security compromises on either connection way.

The scenario is a server with nginx as reverse proxy and a web service bound to localhost. This service shall be available over the Tor Onion Service network. The purpose of nginx is an additional security and abstraction layer between an Internet user and the web service software on the server. The Tor demon shall run on the same server.

Installing a Tor demon as Onion Service and pointing it straight to the web service is doable and probably the first idea one has, though it is not the best idea. This approach would mitigate the reverse proxy and gives an attacker an additional attack vector.

Therefore all web traffic dedicated to the web service has to pass the reverse proxy first.

Another stumbling block are absolute self referencing URLs which lead users away from the Onion Service to regular reachable sites in the Web. A hard coded menu navigation URL to another sub site, for example, can mess things up.

Therefore all HTML content has to be checked and absolute self referencing URLs have to be rewritten. This article will cover both topics.

Traffic flow

As mentioned above, regular traffic from the Internet goes over the reverse proxy and from there to the web service. Traffic from the Tor Onion Service shall follow the same way:

Web traffic flow

Because a Tor Onion Service has slightly different properties than regular traffic, a new server listener configuration is recommendable. In nginx a copy of the existing web server to start with is just fine.

These changes are necessary to this new copy:

  1. Add a listen 11337; statement to advice nginx to open this port.
  2. Change the server_name attribute to localhost so nginx listens now on the localhost interface on port 11337.
  3. Remove all TLS settings in this server configuration.

Because we use a copy of the original and working nginx server listener this one also points to the correct web service already and is workable. A configuration reload of nginx is required.

Now you can configure your torrc to point to localhost:11337:

HiddenServiceDir /var/lib/tor/hidden_service/  
HiddenServicePort 80  

After a restart of the Tor demon the .onion address is generated under /var/lib/tor/hidden_service/hostname

A word about TLS on Onion Services

Tor Onion Services have a built in end to end encryption. Nevertheless a X.509 certificate for an .onion address and with this a TLS encryption layer is still desirable, mainly for two reasons: (1) In the past all of us tried to teach our beloved ones to use HTTPS and watch for the lock in a browser's address bar, now you do not want to explain everyone this is obsolete when using Tor because of the underlying encryption system Tor uses. (2) Modern Browsers handle content and interaction with HTTPS sites more securely then with non-HTTPS sites.

Currently the CA / Browser Forum permits only extended validation (EV) certificates for .onion addresses. Let’s Encrypt does not (yet?) support EV certificates.

Facebook, as probably the first who had a X.509 certificate on its .onion address uses one from DigiCert.

URL rewriting

A potential privacy issue are hard coded URL self references. If an Onion Service user clicks on a link that points to the regular domain name the browser switches form the onion service to the regular version. The user still has all anonymity properties that the Tor network provides, but might not realise that he/she now surfs on a regular web instead of the onion service.

An HTML response URL rewriting modification on a reverse proxy is a possible solution approach. nginx offers a built in module called ngx_http_sub_module which does exactly this. The command nginx -V prints a list of all compiled and loaded modules and should also print out --with-http_sub_module.

The following lines into location / server listener configuration does the rewriting:

proxy_set_header Accept-Encoding "";  
sub_filter '' 'http://6psqayr6b66etmya.onion/';  
sub_filter_once off;  

The configuration change has to be reloaded.

Based on this implementation design approach I enabled Tor Onion Service on my blog: http://6psqayr6b66etmya.onion/.
If you are interested in further reading I recommend these two links: