My rtorrent setup

I hate leaving my main desktop turned on at night. I’m pretty sure it has something to do with the fact that it’s: a) noisy, b) in the same room I sleep in, and I: c) can’t sleep with a lot of noise or light in my room. This is why I have a seperate computer (without monitor that is) in another room that does all the stuff I’d want to be doing at night. It used to record from the TV channel, but dutch television sucks when it comes to good series, so a while ago I put a torrent client on there. Here’s a description of my old setup, and how I set it up today.

Originally it worked like this: I had several directories where FlexGet or I myself would dump torrent files. All these directories were listed in rtorrent as watch directories, with each a seperate directory they got moved to when finished. This worked pretty well, but it had some shortcomings:

  • It never cleaned up the torrent files, so at this point I had well over 200 .torrent files that I really no longer needed, but still existed on the drive.
  • I only had some directory for “general” torrents that I could manually deposit torrents in. This way games, linux distro’s, movies, everything I manually downloaded ended up in the same directory.
  • rtorrent never actually erased any of the torrents. It stopped seeding at a point, but it would still exist in the overview. Because of this the rtorrent overview was a mess, and the only way to really make any sense out of it was to look at the “Incomplete” tab only.
  • If I wanted to add a new RSS feed with a seperate directory, I had to adjust bot the flexget config and the rtorrent config, and restart rtorrent.

Now, as tvrss shut down, and I needed to reconfigure a few things, so I figured I’d better completely re-do the setup. What I came up with were a few PHP scripts that would fire on several rtorrent events and do all the hard work. Here’s my current setup:

  • /media/tb/.rtorrent/data/: this directory holds all the data that rtorrent downloads or has downloaded. Incomplete files are actually in here, complete files will be symlinks to their final destination(s)
  • /media/tb/.rtorrent/incoming/: This is the directory that holds all completed files
  • /media/tb/.rtorrent/session/: rtorrent session info
  • /media/tb/.rtorrent/torrentdump/: This is where I can dump torrents for manual downloading. Files will end up in /media/tb/.rtorrent/incoming/Dump. It also works with recursive dirs: all torrents in /media/tb/.rtorrent/torrentdump/A/B/C will be downloaded to /media/tb/.rtorrent/incoming/Dump/A/B/C.
  • /media/tb/.rtorrent/torrents/: This holds the torrents actually being downloaded, as well as a .info file for each torrent holding information on what should happen with it after it’s completed.
  • /media/tb/.rtorrent/ A PHP script that will scan the torrentdump directory, move the torrents to the torrents directory, and create .info files for each
  • /media/tb/.rtorrent/ A PHP script that will be fired by rtorrent once a download finishes. This will move the downloaded data to it’s destination directory, leave a symlink in the data directory, fix up permissions, and remove the torrent and info file.
  • /media/tb/.rtorrent/rtorrent.rc: rtorrent config file.
  • /media/tb/Incoming: symlink to /media/tb/.rtorrent/incoming
  • /media/tb/TorrentDump: symlink to /media/tb/.rtorrent/torrentdump

Let’s start with


function scan($directory,$outputpath,&$todo) {
	if ($d!==FALSE) {
		while (FALSE !== ($file=readdir($d))) {
			if ($file[0]==".") {
			} else {
				if (is_dir($path)) {
				} else if (substr($path,strlen($path)-strlen($suffix))==$suffix) {
foreach ($todo as $torrent=>$info) {
	exec("mv -f ".escapeshellarg($torrent)." ".escapeshellarg($target));

the “scan” function recursively scans a directory for torrent files. Every torrent file it finds will be put in the $todo array, together with the destination directory (and maybe other info). The main script will call this on /media/tb/.rtorrent/torrentdump, together with a “Dump” directory as output dir. After that, it will create a .info file for each torrent first (which contains the serialized data from the scan function), and then move the torrent to the torrents dir for rtorrent to download.

Next up:

if (!file_exists($infofile))
	die("Nothing to do");
foreach ($pathsplit as $pathbit) {
	//Make new directory (with chmod and all)
	exec("mkdir -m 777 ".escapeshellarg($newdir));
//Move data
exec("mv -f ".escapeshellarg($datapath)." ".escapeshellarg($newpath));
//Chmod data
exec("chmod -R 666 ".escapeshellarg($newpath));
//Link old data to new data
exec("ln -s ".escapeshellarg($newpath)." ".escapeshellarg($datapath));
//Remove torrent file (will have rtorrent remove symlink)
exec("rm -rf ".escapeshellarg($torrent));
//Remove info file
exec("rm -rf ".escapeshellarg($infofile));

This gets both the torrent file ($argv[1]), and the download path ($argv[2]). It tries to open the .info file, and then creates the needed directories (as read/write for everyone), moves the file, chmods it read/write, set up a symlink on the old location (for rtorrent to keep seeding), and removes the torrent and info files.

rtorrent config follows:

# This is an example resource file for rTorrent. Copy to
# ~/.rtorrent.rc and enable/modify the options as needed. Remember to
# uncomment the options you wish to enable.

# Maximum and minimum number of peers to connect to per torrent.
#min_peers = 40
#max_peers = 100

# Same as above but for seeding completed torrents (-1 = same as downloading)
#min_peers_seed = 10
#max_peers_seed = 50

# Maximum number of simultanious uploads per torrent.
#max_uploads = 15

# Global upload and download rate in KiB. "0" for unlimited.
#download_rate = 0
#upload_rate = 0

# Default directory to save the downloaded torrents.
directory = /media/tb/.rtorrent/data/

# Default session directory. Make sure you don't run multiple instance
# of rtorrent using the same session directory. Perhaps using a
# relative path?
session = /media/tb/.rtorrent/session/

# Watch a directory for new torrents, and stop those that have been
# deleted.
#schedule = watch_directory,5,5,load_start=./watch/*.torrent
#schedule = untied_directory,5,5,stop_untied=

# Close torrents when diskspace is low.
#schedule = low_diskspace,5,60,close_low_diskspace=100M

# Stop torrents when reaching upload ratio in percent,
# when also reaching total upload in bytes, or when
# reaching final upload ratio in percent.
# example: stop at ratio 2.0 with at least 200 MB uploaded, or else ratio 20.0
#schedule = ratio,60,60,"stop_on_ratio=200,200M,2000"

# The ip address reported to the tracker.
#ip =
#ip =

# The ip address the listening socket and outgoing connections is
# bound to.
#bind =
#bind =

# Port range to use for listening.
port_range = 12300-12400

# Start opening ports at a random position within the port range.
port_random = yes

# Check hash for finished torrents. Might be usefull until the bug is
# fixed that causes lack of diskspace not to be properly reported.
check_hash = no

# Set whetever the client should try to connect to UDP trackers.
use_udp_trackers = yes

# Alternative calls to bind and ip that should handle dynamic ip's.
#schedule = ip_tick,0,1800,ip=rakshasa
#schedule = bind_tick,0,1800,bind=rakshasa

# Encryption options, set to none (default) or any combination of the following:
# allow_incoming, try_outgoing, require, require_RC4, enable_retry, prefer_plaintext
# The example value allows incoming encrypted connections, starts unencrypted
# outgoing connections but retries with encryption if they fail, preferring
# plaintext to RC4 encryption after the encrypted handshake
encryption = allow_incoming,try_outgoing,enable_retry

# Enable DHT support for trackerless torrents or when all trackers are down.
# May be set to "disable" (completely disable DHT), "off" (do not start DHT),
# "auto" (start and stop DHT as needed), or "on" (start DHT immediately).
# The default is "off". For DHT to work, a session directory must be defined.
dht = on

# UDP port to use for DHT. 
dht_port = 12300

# Enable peer exchange (for torrents not marked private)
peer_exchange = yes

# Do not modify the following parameters unless you know what you're doing.

# Hash read-ahead controls how many MB to request the kernel to read
# ahead. If the value is too low the disk may not be fully utilized,
# while if too high the kernel might not be able to keep the read
# pages in memory thus end up trashing.
#hash_read_ahead = 10

# Interval between attempts to check the hash, in milliseconds.
#hash_interval = 100

# Number of attempts to check the hash while using the mincore status,
# before forcing. Overworked systems might need lower values to get a
# decent hash checking rate.
#hash_max_tries = 10

# Set the umask for this process, which is applied to all files created by the program.
# umask = 0666

# Watches
# Scan torrent dump, and watch for torrents
schedule = watch_directory,10,10,"execute=/media/tb/.rtorrent/;load_start=/media/tb/.rtorrent/torrents/*.torrent"
# Watch for torrent files being deleted
schedule = watch_untied,10,10,"remove_untied="
# If a torrent is erased, erase the contents
on_erase = rm_files,"execute=rm,-rf,--,$d.get_base_path="
# Run oncompleted on a completed torrent
on_finished = linkup,"execute=/media/tb/.rtorrent/,$d.get_tied_to_file=,$d.get_base_path="

# Throthling
schedule = throttle_high,01:00:00,24:00:00,"download_rate=0;upload_rate=50"
schedule = throttle_low,09:00:00,24:00:00,"download_rate=500;upload_rate=20"
schedule = ratio,10,10,"stop_on_ratio=1,1K,2"

# XMLRPC for wTorrent
scgi_port = localhost:5000

Some of the notable changes from a default rtorrent setup:

  • Before watching the watch directory, it calls
  • After the torrent file is deleted, it’s removed (= completed files stop seeding)
  • If a torrent is erased, the data is erased. Note that if a torrent is completed, the data is merely a symlink, so no completed torrents will be deleted
  • On finished, will be run

So, this is a much better setup than I had already. Some of the things I still need to fix:

  • Set up FlexGet to download series, and create appropriate .info files for each torrent
  • Find a way to have untied torrents seed for a while, and remove after a certain seeding threshold

4 thoughts on “My rtorrent setup”

  1. Great stuff!

    I recently set up my own server and am using rtorrent on it.
    However I was also annoyed by the mess in the overview and the .torrent files left behind.

    Inspired by your scripts I started out applying them to my own situation.
    Besides changing some directory paths in the scripts I figured out that I needed php-cli to get them running.

    I tested the scripts by running them manually and everything worked fine. However when I ran rtorrent it gave me this message:

    “Scheduled command failed: watch_directory: Bad return code.”

    After about two hours of messing around I changed:

    “schedule = watch_directory,10,10,”execute=/directory/containing/;load_start=/directory/containing/*.torrent”


    schedule = watch_directory,10,10,”execute=/directory/containing/”
    schedule = watch_directory,15,15,”load_start=/directory/containing/*.torrent”

    Which seemed to do the trick.

    Thanks for posting this!



  2. Interesting setup, I’ll be using something similar for my machine.
    I just noticed a minor flaw:

    port_range = 12300-12400

    dht_port = 12300

    You might want to alter that port range to 12301, just for that 1% chance it picks the same port 😉

Leave a Reply

Your email address will not be published. Required fields are marked *