Skip to content

Usage on Tomato routers

Martinique edited this page Sep 21, 2024 · 12 revisions

This article describes using a router with Linux-based Tomato firmware to run name-based HTTPS reverse proxies with Let's Encrypt certificates, using acme.sh, providing encrypted access to home or small business LAN services from outside (untrusted) networks, such as your mobile devices.

Traffic to HTTPS port(s) (the usual 443 or whatever you use) in your public IP address will be forwarded to plain HTTP services on your LAN hosts with your Tomato router functioning as a reverse proxy. This way you can have multiple (sub)domains in a single public port pointed to several LAN servers with Tomato handling all the HTTPS work, which is not possible with simple port forwarding. A configuration example is provided.

Much of the setup is done through SSH, but you'll also need Tomato's web interface, marked in this guide as Menu→Submenu.

Prerequisites

  • A router with USB ports running FreshTomato or another recent Tomato fork with a fully featured OpenSSL and web server. A fast CPU and large NVRAM are recommended. There's an unconfirmed report of MIPS-based routers having problems, possibly because of missing ext4 support, but ext3 or ext2 can be used instead.
  • Unless you happen to have a static public IP, you need a dynamic DNS (Basic→DDNS) service configured in Tomato. Some DNS services also provide API control, enabling DNS mode for acme.sh. You can point additional regular CNAME records to the DDNS hostname, so not all your hostnames need to be dynamic. In this guide tomato.example.com and www.tomato.example.com are used as examples.
  • At least one plain HTTP web service or site running on either a LAN host or Tomato itself. It's a good idea to assign static IP addresses for servers (Basic→Static DHCP/ARP/IPT or Basic→DHCP Reservation).

If you're going to issue certificates using webroot mode, Tomato's web server must be running in port 80, so make sure your service provider doesn't block that port and that the web admin service (Administration→Admin Access) is not using the same port.

Standalone modes won't work, as there's no socat in Tomato (without entware).

Installing

Format a USB HDD or flash drive as ext4 (or ext2 if you don't need journaling) and name the partition as you wish. For this guide the partition was named "flash", so Tomato auto-mounts it to /tmp/mnt/flash. Don't forget to change every path mentioned in this guide to match the name you choose.

You could use Tomato's JFFS partition instead of an external drive, but firmware upgrades may need JFFS disabled, so it's rather inconvenient. Besides, frequent writing may wear it out.

SSH to your Tomato and paste these commands to download and extract acme.sh:

cd /tmp/mnt/flash
wget https://github.com/acmesh-official/acme.sh/archive/master.zip
unzip master.zip
rm master.zip
cd acme.sh-master
chmod +x acme.sh

You're now ready to install. Change the email address before running this install command:

./acme.sh --install --nocron --home /tmp/mnt/flash/acme.sh \
--accountemail "your.email@example.com" --useragent "Tomato router"

Finally remove the installer directory: cd .. && rm -Rf acme.sh-master

The installer wrote a line to the .profile file in the root user's home directory. Tomato keeps this directory on a RAM disk that won't survive reboots, so you need to make this permanent by adding this line to Administration→Scripts→Init:

echo '. "/tmp/mnt/flash/acme.sh/acme.sh.env"' >> /tmp/home/root/.profile

Save the settings. Close the current SSH session and start a new one to activate the change.

Now go to Administration→Scheduler. Scheduled commands ignore the .profile file, so you need to provide the full path to acme.sh and set the directory options. Put this line in one of the custom command fields and set it to run daily, preferrably at a time when there's least traffic:

/tmp/mnt/flash/acme.sh/acme.sh --cron --home /tmp/mnt/flash/acme.sh --config-home /tmp/mnt/flash/acme.sh/conf

Save the scheduler settings.

Configuring Tomato's web server

If you'll only use DNS mode, you don't need to set the port and path; they're for acme.sh's webroot mode.

Go to Web Server→Basic Settings and set it up like this:

  • Check Enable Server on Start and Allow Remote Access
  • Run As: Nobody (running as root is generally a bad idea)
  • Web Server Port: 80
  • Server Root Path: /tmp/mnt/flash/www

Save the settings. If the web server isn't running, click "Start now". Then create the directory for webroot challenges:

mkdir -p /tmp/mnt/flash/www/.well-known/acme-challenge

Issuing certificates

There's nothing Tomato-specific about this, except that you can only use webroot or DNS mode. To avoid tripping failure limits, it's best to do a dry run first by adding the --test flag to the command. If the dry run works, your DNS and router configurations are OK.

Webroot mode:

acme.sh --issue -d tomato.example.com -d www.tomato.example.com -w /tmp/mnt/flash/www

DNS mode (see the guide):

acme.sh --issue -d tomato.example.com -d www.tomato.example.com --dns dns_xxxx

Installing certificates

Create a directory for your new certificate and install it there:

mkdir -p /tmp/mnt/flash/cert/tomato.example.com

acme.sh --install-cert -d tomato.example.com \
--fullchain-file /tmp/mnt/flash/cert/tomato.example.com/fullchain.pem \
--key-file /tmp/mnt/flash/cert/tomato.example.com/key.pem \
--reloadcmd "service enginex restart"

Note that Tomato has a funny quirk, internally calling nginx "enginex".

Since nginx runs as user "nobody" you need to make the chain and key files readable by it. Change their owner group to "nobody" and allow group read permissions:

chown root:nobody /tmp/mnt/flash/cert/tomato.example.com/*
chmod 0640 /tmp/mnt/flash/cert/tomato.example.com/*

Tomato is a single-user system, so you don't need to worry about file permissions much. Just don't put any vulnerable PHP code on the web server; leaking private keys and altering NVRAM would be pretty bad.

Adding a reverse proxy

Modify the below example to match your new hostname(s), certificate path and LAN server IP and port (on the proxy_pass line) and add it to the HTTP Section field in Web Server→Advanced Settings. This is a minimalist reverse proxy example, so read some guides to learn about more options, especially security-related.

If the server is Tomato itself, set proxy_pass to http://127.0.0.1:80 (match the port number with web server setting).

You can add as many proxy server configurations as you wish, but note that they take up precious NVRAM, unless you move the whole nginx configuration to a file, disabling GUI settings (hint: nginx -T). In many cases you can leave out the proxy_set_header lines, as they only provide connection info for logging etc.

server {
  listen 443 ssl;
  server_name tomato.example.com www.tomato.example.com;
  ssl_certificate /tmp/mnt/flash/cert/tomato.example.com/fullchain.pem;
  ssl_certificate_key /tmp/mnt/flash/cert/tomato.example.com/key.pem;
  location / {
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_pass http://192.168.0.3:8080;
    proxy_redirect off;
  }
}

Save the settings and wait for Tomato to restart nginx. You should now be able to access the LAN HTTP server from outside through https://tomato.example.com/

Notes

  • Not all HTTP services are proxy-compatible. For example, absolute URLs can be troublesome, although you can fix them with http_sub filters.
  • This should be obvious, but people can be surprisingly dumb: Don't create public proxy connections to private LAN devices/services without proper password protection.
Clone this wiki locally