Skip to content

Using Onionspray

This guide goes beyond the quick start setup laid out in the tutorial and covers the next steps after installation.

Check the command line syntax page for the full usage.

Project structure

As explained in the building blocks section, Onionspray sites are configured through projects.

Each project is composed of the following items:

  1. Onion Service keys stored at the secrets/ folder:
    • Yes, Onion Service keys from all projects are stored in the same folder.
  2. A project folder stored at projects/<project-name> with:
    • HTTPS certificates, stored at projects/<project-name>/ssl.
    • Log files stored at projects/<project-name>/log.
    • Configuration files and scripts.
  3. A configuration file, like <project-name>.conf:
    • This is were Onionspray is tuned for the specific project.
    • Every time a project is reconfigured, scripts and configuration files in the project folder are updated according to this main configuration.
  4. An optional template, like <project-name>.tconf:
    • This file is only used when <project-name>.conf does not exist.
    • The only difference between the template and a regular configuration file is that templates supports placeholders such as those to generate Onion Service addresses on the fly when configuring a project.

Creating a project

A project can be created in three ways:

  1. Using the ./onionspray create command, as explained in the tutorial.
  2. Using a template file ending in .tconf, when the Onion Service keys still need to be generated.
  3. Using a configuration file ending in .conf, when the Onion Service keys already exists.

All three ways ends up generating a .conf file, which can later be edited and used to reconfigure a project.

Which way to use depends on what are your needs. When unsure, start with the create command detailed in the tutorial. The rest of this section will focus on the template (.tconf) approach.

Regarding the Onion Service keys, there are two ways to proceed:

  1. With automatically-generated addresses.
  2. With manually-mined (or pre-existing) addresses.

With automatically-generated Onions Service addresses

Random addresses

Random addresses are created with the %NEW_V3_ONION% placeholder in project templates.

Create a config file with a .tconf suffix -- we'll pretend it's foo.tconf -- and use this kind of syntax:

set project myproject
hardmap %NEW_V3_ONION% foo.com
hardmap %NEW_V3_ONION% foo.co.uk
hardmap %NEW_V3_ONION% foo.de

... and then run

./onionspray config foo.tconf

... which will create the onions for you and will populate a foo.conf for you, and it will then configure Onionspray from that. You should probably delete foo.tconf afterwards, since forcibly reusing it would trash your existing onions.

Vanity addresses

Vanity addresses are created with the %NEW_V3_VANITY_ONION_% placeholder in project templates. The syntax is slightly different from the %NEW_V3_ONION% because %NEW_V3_VANITY_ONION_% requires an argument specifying the initial text prefix for the generated .onion address.

Create a config file with a .tconf suffix -- we'll pretend it's foo.tconf -- and use this kind of syntax:

set project myproject
hardmap %NEW_V3_VANITY_ONION_foo% foo.com
hardmap %NEW_V3_VANITY_ONION_bar% foo.co.uk
hardmap %NEW_V3_VANITY_ONION_baz% foo.de

... and then run

./onionspray config foo.tconf

... which will create the onions for you and will populate a foo.conf for you, and it will then configure Onionspray from that. You should probably delete foo.tconf afterwards, since forcibly reusing it would trash your existing onions.

Note that the generated .onion for foo.com will begin with foo, since the %NEW_V3_VANITY_ONION_foo% placeholder was used; similarly, the .onion for foo.co.uk and foo.de will begin, respectively, with bar and baz.

With manually-mined Onion Service addresses

Generating the key

Random addresses

A random .onion address can be manually generated with onionspray genkey:

  • Run onionspray genkey -- it will print the name of the onion it generates.
  • Run it as many times as you wish/need.
Vanity addresses

Vanity addresses can be manually generated in a number of ways:

  • With onionspray genkey-vanity <filter>:

    • Run onionspray genkey-vanity <filter> -- it will print the name of the onion it generates.
    • Run it as many times as you wish/need.
    • Example onionspray genkey-vanity test -- it will generate an address beginning with test.
    • This uses Onionmine under the hood.
  • Using and alternative tool or mkp224o directly:

    • Make sure to store your mined keys in secrets.d, naming the keys like someverylongonionaddressinvolvingalotofbase23characterss.v3pub.key and someverylongonionaddressinvolvingalotofbase32characterss.v3sec.key

Check also the vanity guide for further details.

Creating the config file

  • Create a config file with a .conf suffix - we'll pretend it's foo.conf - and use this kind of syntax, substituting a2s3c4d5e6f7g8h9 for the onion address that you generated.

    set project myproject hardmap z2walkyky3zzv52g3gedrhxa665qdrslyvrljg5wppibx34olslnxgqd foo.com

... and then (IMPORTANT) run:

./onionspray config foo.conf

... which will configure Onionspray.

Starting a project

Like this:

./onionspray start myproject

Starting all projects at once is also possible:

./onionspray start -a

Restarting a project

Restarting a project can be done with:

./onionspray restart myproject

To restart all projects, use

./onionspray restart -a

Reconfiguring a project

A project can be reconfigured by editing <project-name>.conf and then running

./onionspray configure <project-name>.conf

Configuration options

Project settings are documented in the example configuration.

Subdomains management

Overview

Subdomains are supported like this, for dev as an example:

set project myproject
hardmap z2walkyky3zzv52g3gedrhxa665qdrslyvrljg5wppibx34olslnxgqd foo.com dev

... and if you have multiple subdomains:

hardmap z2walkyky3zzv52g3gedrhxa665qdrslyvrljg5wppibx34olslnxgqd foo.com dev blogs dev.blogs [...]

How it works

When you are setting up the mappings in a config file, you may have to accommodate "subdomains"; the general form of a internet hostname is like this:

  • hostname.domain.tld # like: www.facebook.com or www.gov.uk
  • or: hostname.domain.sld.tld # like: www.amazon.co.uk
  • hostname.subdom.domain.tld # like: www.prod.facebook.com
  • hostname.subsubdom.subdom.domain.tld # cdn.lhr.eu.foo.net
  • hostname.subsubsubdom.subsubdom.subdom.domain.tld # ...

... and so on, where:

  • tld = top level domain
  • sld = second level domain
  • domain = generally the name of the organisation you are interested in
  • subdomain = some kind of internal structure
  • hostname = actual computer, or equivalent

When you are setting up mappings, generally the rules are:

  • you will map one domain per onion
  • you will ignore all hostnames
  • you will append all possible subdomain stems

So if your browser tells you that you are fetching content from cdn7.dublin.ireland.europe.foo.co.jp, you should add a line like:

hardmap %NEW_V3_ONION% foo.co.jp europe ireland.europe dublin.ireland.europe

... and Onionspray should do the rest. All this is necessary purely for correctness of the self-signed SSL-Certificates - which are going to be weird, anyway - and the rest of the HTML-rewriting code in Onionspray will be blind to subdomains.

Managing lots of sites and subdomains

Example:

  • www.foo.com.au
  • www.syd.foo.com.au
  • www.per.foo.com.au,
  • www.cdn.foo.net
  • www.foo.aws.amazon.com
  • ...

Put them all in the same project as separate mappings, remembering to avoid the actual "hostnames" as described above:

set project fooproj
hardmap %NEW_V3_ONION% foo.com.au syd per
hardmap %NEW_V3_ONION% foo.net cdn
hardmap %NEW_V3_ONION% foo.aws.amazon.com

Onion mapping/translations will be applied for all sites in the same project.

System startup and housekeeping procedures

Once you have installed Onionspray and configured and tested it for your project, it's time to make sure the needed services will starting during system initialization.

There are three approaches to do this.

Init-like startup scripts

Onionspray generates init-like startup scripts through this command:

./onionspray make-scripts

This will create two files:

  • ./onionspray-init.sh: for installing on your system as a startup script
  • ./onionspray-housekeeping.sh: for cronjob log rotation and other cleanup work

Please read the individual files for installation instructions; local setup is intended to be pretty simple.

Setting up a systemd service file

For Systemd, you'll have to create a file under /etc/systemd/system/onionspray.service with proper ownership/permissions (usually root:root and 0644), and activate it.

Example based on the Onionspray Ansible Role systemd configuration (adapt to where Onionspray is installed):

#
# Onionspray Systemd service file
#
# Copyright (C) 2025 The Tor Project, Inc.
# SPDX-License-Identifier: AGPL-3.0-or-later
#
[Unit]
Description=Onionspray
[Service]
Type=oneshot
ExecStart=/home/onionspray/onionspray/onionspray-init.sh start
ExecStop=/home/onionspray/onionspray/onionspray-init.sh stop
RemainAfterExit=true
[Install]
WantedBy=multi-user.target

Once this file has been set, proceed by enabling it:

sudo systemctl daemon-reload
sudo systemctl start onionspray
sudo systemctl enable onionspray

Using cron to manage Onionspray

Another approach for system initialization is using cron.

The following example is based on Onionspray Ansible Role cron configuration.

Create a /etc/cron.d/onionspray-service with proper ownership/permissions (usually root:root and 0644) and the following contents (adapt to where Onionspray is installed and to the corresponding user):

#
# Crontab acting as Onionspray's service initialization
#
# Copyright (C) 2025 The Tor Project, Inc.
# SPDX-License-Identifier: AGPL-3.0-or-later
#

# Use bash to run commands
SHELL=/bin/bash

# Start Onionspray on system reboot
@reboot onionspray (cd /home/onionspray ; ./onionspray clean -a ; ./onionspray bounce -a) &>/dev/null

Whenever the machine reboots and cron starts running, it will initialize Onionspray.

Log rotation

Log rotation can be configured in Onionspray using two approaches.

Built-in log rotation

All existing logs in Onionspray can be rotated with:

./onionspray logrotate

This command does not rotates logs periodically, so it needs to be included in a regular procedure. For the init-like startup procedure, it is already invoke by the "house keeping" script.

Using logrotate directly

Another approach for log rotation is to use a specialized tool such as logrotate, which is available as a package for a number of systems.

The following example is based on the Onionspray Ansible Role logrotate configuration.

First, create a /etc/logrotate.d/onionspray with proper ownership/permissions (usually root:root and 0644) and the following contents (adapt to your needs, like where Onionspray is installed, the user running Onionspray, rotation frequencies and number of times):

#
# Onionspray log rotation config
#
# Copyright (C) 2025 The Tor Project, Inc.
# SPDX-License-Identifier: AGPL-3.0-or-later
#

# Main logs
/home/onionspray/log/*.log {
  su onionspray onionspray
  create 644 onionspray onionspray
  weekly
  dateext
  rotate 52
  missingok
  ifempty
  compress
}

# Onionbalance logs
/home/onionspray/onionbalance/*.log {
  su onionspray onionspray
  create 644 onionspray onionspray
  weekly
  dateext
  rotate 52
  missingok
  ifempty
  compress
  lastaction
    /home/onionspray/onionspray ob-reload -a
  endscript
}

# Project logs
/home/onionspray/projects/*/log/*.log {
  su onionspray onionspray
  create 644 onionspray onionspray
  daily
  dateext
  rotate 365
  missingok
  ifempty
  compress
  lastaction
    # Reload Onionspray
    /home/onionspray/onionspray --local nxreload -a
    /home/onionspray/onionspray --local torreload -a
  endscript
}

This file should be enough if rotation intervals are equal or greater than a day, which is the usual frequency logrotate runs.

If you need smaller intervals, such as hourly rotations, make sure to also create the /etc/cron.d/onionspray-logs file with proper ownership/permissions (usually root:root and 0644) and the following contents:

#
# Crontab for Onionspray's log rotation
#
# Copyright (C) 2025 The Tor Project, Inc.
# SPDX-License-Identifier: AGPL-3.0-or-later
#

# Use bash to run commands
SHELL=/bin/bash

# Trigger logrotate for each Onionspray project every hour
0 * * * * root /usr/sbin/logrotate /etc/logrotate.d/onionspray &>/dev/null