Skip to main content

Getting Isso to work on CentOS 6 and nginx

Isso is an open source alternative to Disqus. Users can post anonymous comments and everything is stored in an SQLite database on your server.

The server is written in Python and the client is pure JavaScript.

Warning: this post explains how I got Isso to work on my server, this is not aimed to be a reference guide! There probably are some installation procedures to improve and maybe some bugs to report.


They provide prebuilt packages for most GNU/Linux distributions but, unfortunately, not for CentOS (which is powering my VPS). So I had to install Isso with pip.

First install virtualenv, Python development headers, SQLite and C compiler with yum:

# yum install python-setuptools python-virtualenv python-devel sqlite
# yum groupinstall "Development tools"

Then create a new directory in /opt and chown it to your user and group:

# mkdir /opt/isso
# chown <user>:<group> /opt/isso

Activate virtualenv as regular user:

$ virtualenv /opt/isso
$ source /opt/isso/bin/activate

We can now install Isso with pip:

$ pip install isso


Note that I encountered some problems while installing the isso package.

First, the libffi package was not found by misaka (an isso dependency), I had to install libffi-devel:

# yum install libffi-devel

Then, the current version of the misaka package needed by isso wouldn't build, so I had to force the installation of a previous version. This will probably soon be fixed, but just in case, this is what I did:

$ pip install misaka==1.0.2

I also had to install argparse:

$ pip install argparse

And finally, this exception appeared when running the server:

Exception happened during processing of request from ('', 55431)
Traceback (most recent call last):
  File "/usr/lib64/python2.6/", line 570, in process_request_thread
    self.finish_request(request, client_address)
  File "/usr/lib64/python2.6/", line 332, in finish_request
    self.RequestHandlerClass(request, client_address, self)
  File "/usr/lib64/python2.6/", line 627, in __init__
  File "/opt/isso/lib/python2.6/site-packages/werkzeug/", line 217, in handle
    rv = BaseHTTPRequestHandler.handle(self)
  File "/usr/lib64/python2.6/", line 329, in handle
  File "/opt/isso/lib/python2.6/site-packages/werkzeug/", line 252, in handle_one_request
    return self.run_wsgi()
  File "/opt/isso/lib/python2.6/site-packages/werkzeug/", line 201, in run_wsgi
    traceback = get_current_traceback(ignore_system_exceptions=True)
  File "/opt/isso/lib/python2.6/site-packages/werkzeug/debug/", line 184, in get_current_traceback
    tb = Traceback(exc_type, exc_value, tb)
  File "/opt/isso/lib/python2.6/site-packages/werkzeug/debug/", line 235, in __init__
    self.frames.append(Frame(exc_type, exc_value, tb))
  File "/opt/isso/lib/python2.6/site-packages/werkzeug/debug/", line 402, in __init__
    self.filename = to_unicode(fn, get_filesystem_encoding())
  File "/opt/isso/lib/python2.6/site-packages/werkzeug/", line 62, in get_filesystem_encoding
    'filesystem encoding instead of {!r}'.format(rv),
ValueError: zero length field name in format

I just removed the line 62 in /opt/isso/lib/python2.6/site-packages/werkzeug/ Dirty as hell, but hey, it's working.


Let's create a configuration file for Isso in, for example, /opt/isso/etc/isso.cfg with the following:

; database location, check permissions, automatically created if not exists
dbpath = /opt/isso/comments.db
; your website or blog (not the location of Isso!)
host =

listen = http://localhost:8080/

You can launch Isso with:

$ isso -c /opt/isso/etc/isso.cfg run

The last thing we need to do is to configure nginx as a proxy to the Isso server.

The official documentation suggests that you use comments.example.tld as server name. I prefer www.example.tld/comments as it doesn't need another certificate when using HTTPS.

Add a location block in your nginx config and tell nginx to pass requests beginning with /comments to the local Isso server:

server {

    listen          443 ssl spdy;
    listen          [::]:443 ssl spdy;

    ssl_certificate /path/to/chained.pem;
    ssl_certificate_key /path/to/domain.key;

    root            /usr/share/nginx/html/www;

    location /comments {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Script-Name /comments;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://localhost:8080;

We're done! We can now tweak the configuration file to activate moderation queue, email notifications, etc.

Nginx: Redirect non-www and/or HTTP traffic to HTTPS and www domain

As you may have seen, I have now forced the use of HTTPS on my website. The certificate is signed by Let's Encrypt and should therefore be recognized by all major browsers. I used the letsencrypt-nosudo tool made by Daniel Roesler to manually generate my certificates because I didn't want the letsencrypt client to mess with my Nginx config files (I will probably try when I'm sure it is stable enough and working well on my server OS).

I wanted to redirect all traffic coming from the non-www domain to the www domain, and all HTTP traffic to HTTPS.

Basically, like this:

From To

Here is the configuration I use on my server:

# Redirect all non-www traffic to www
server {
    listen          80;
    listen          443 ssl spdy;
    listen          [::]:80;
    listen          [::]:443 ssl spdy;

    ssl_certificate /path/to/certs/chained.pem;
    ssl_certificate_key /path/to/certs/domain.key;

    return          301$request_uri;

# Redirect all non-encrypted traffic to encrypted
server {
    listen          80;
    listen          [::]:80;

    return          301$request_uri;

# Main server block
server {
    listen          443 ssl spdy;
    listen          [::]:443 ssl spdy;

    ssl_certificate /path/to/certs/chained.pem;
    ssl_certificate_key /path/to/certs/domain.key;

    root            /usr/share/nginx/html/www;

    # ...

Note that I'm using a SAN (Subject Alternative Name) certificate which is valid for both and

chained.pem contains this certificate, along with the "Let’s Encrypt Intermediate X1" certificate.

domain.key contains the certificate private key.

Compose Key

The Compose Key allows you to input special characters by pressing two or more subsequent keys. For example, pressing Compose then / + = will print a (not equal to) sign.

Choose a key in the list:

$ grep "compose:" /usr/share/X11/xkb/rules/base.lst

Then add the following line in your .xinitrc (change <key> to the key you want):

$ setxkbmap -option compose:<key>

Some useful combinations not directly accessible on my keyboard:

<Multi_key> <minus> <minus> <period>    : "–"   U2013 # EN DASH
<Multi_key> <minus> <minus> <minus>     : "—"   U2014 # EM DASH
<Multi_key> <period> <equal>            : "•"   enfilledcircbullet # BULLET
<Multi_key> <slash> <o>                 : "ø"   oslash # LATIN SMALL LETTER O WITH STROKE
<Multi_key> <o> <a>                     : "å"   aring # LATIN SMALL LETTER A WITH RING ABOVE
<Multi_key> <slash> <equal>             : "≠"   U2260 # NOT EQUAL TO
<Multi_key> <less> <equal>              : "≤"   U2264 # LESS-THAN OR EQUAL TO
<Multi_key> <greater> <equal>           : "≥"   U2265 # GREATER-THAN OR EQUAL TO
<Multi_key> <d> <i>                     : "⌀"   U2300 # DIAMETER SIGN
<Multi_key> <8> <8>                     : "∞"   U221e # 8 8 INFINITY

To configure the combinations:

$ cp /usr/share/X11/locale/en_US.UTF-8/Compose ~/.XCompose

How-To: Install Arch Linux on an encrypted Btrfs partition

These are the steps I followed to install Arch Linux on my SSD. There are two partitions: /dev/sda1 for /boot and /dev/sda2 for the rest of the system. The last one is encrypted with dm-crypt and formatted in Btrfs. The first one stays unencrypted as it is needed by the BIOS to boot the system (although it seems possible to have it encrypted too, but I didn't try).

Please use common sense and do not copy/paste commands without first trying to understand what they do! And also remember to backup everything: I am not responsible for any loss of data!

I consider that you just booted on your Arch Linux install medium (note that the "SSD preparation" step can be done from an already installed system as it might take some time).

Keyboard layout

# loadkeys be-latin1

SSD preparation

We will fill up the SSD with random data.

Create a temporary encrypted container on /dev/sda:

# cryptsetup open --type plain /dev/sda container

Check it exists:

# fdisk -l
Disk /dev/mapper/container: 232.9 GiB, 250059350016 bytes, 488397168 sectors

Wipe it with pseudorandom encrypted data (use of /dev/urandom is not required as the encryption cipher is used for randomness):

# dd if=/dev/zero of=/dev/mapper/container

(Took me 1 hour on my 250 GB Samsung 840 EVO)

Close the container:

# cryptsetup luksClose container


Create two GPT partitions:

# gdisk /dev/sda
  • /dev/sda1: 128 MiB for /boot
  • /dev/sda2: the remaining space for the system

Prepare boot partition:

# mkfs.ext2 /dev/sda1

Prepare encrypted partition:

# cryptsetup --cipher aes-xts-plain64 --hash sha512 --use-random --verify-passphrase luksFormat /dev/sda2

Open encrypted partition:

# cryptsetup luksOpen /dev/sda2 root

Format encrypted partition in Btrfs:

# mkfs.btrfs /dev/mapper/root
# mount -o noatime,discard,ssd,defaults /dev/mapper/root /mnt

Create Btrfs subvolumes:

# cd /mnt
# btrfs subvolume create __active
# btrfs subvolume create __active/rootvol
# btrfs subvolume create __active/home
# btrfs subvolume create __active/var
# btrfs subvolume create __snapshots

System configuration

Mount subvolumes in /mnt:

# cd
# umount /mnt
# mount -o subvol=__active/rootvol /dev/mapper/root /mnt
# mkdir /mnt/{home,var}
# mount -o subvol=__active/home /dev/mapper/root /mnt/home
# mount -o subvol=__active/var /dev/mapper/root /mnt/var

Mount boot partition:

# mkdir /mnt/boot
# mount /dev/sda1 /mnt/boot

Install system

# pacstrap /mnt base base-devel btrfs-progs

Generate fstab

# genfstab -p /mnt >> /mnt/etc/fstab

Edit /mnt/etc/fstab and add the following options:

/dev/mapper/root    /       btrfs   noatime,discard,ssd,autodefrag,compress=lzo,space_cache,subvol=__active/rootvol ...
/dev/mapper/root    /home   btrfs   noatime,discard,ssd,autodefrag,compress=lzo,space_cache,subvol=__active/home    ...
/dev/mapper/root    /var    btrfs   noatime,discard,ssd,autodefrag,compress=lzo,space_cache,subvol=__active/var     ...
/dev/sda1           /boot   ext2    ...

Chroot into the new system

# arch-chroot /mnt

Configure hostname, timezone, locale, keymap, etc.

Add the encrypt hook in /etc/mkinitcpio.conf (before filesystems) so that it looks something like this:

HOOKS="base udev autodetect modconf block encrypt filesystems keyboard fsck"

Rebuild the boot images:

# mkinitcpio -p linux

Set root password:

# passwd

Configure SYSLINUX:

# pacman -S syslinux gptfdisk
# syslinux-install_update -iam

Edit /boot/syslinux/syslinux.cfg:

LABEL arch
    APPEND root=/dev/mapper/root rootflags=subvol=__active/rootvol cryptdevice=/dev/sda2:root rw

Exit chroot.

Unmount everything:

# umount /mnt/{home,var,boot}
# umount /mnt


# reboot

Markdown linebreaks

Adding a simple linebreak in Markdown is definitely not intuitive. When you write:

Roses are red
Violets are blue

It will render:

Roses are red Violets are blue

If you write a newline between the two:

Roses are red

Violets are blue

It will render two paragraphs:

Roses are red

Violets are blue

The thing is to put two blank spaces at the end of the first line:

Roses are red␣␣
Violets are blue

It will then render:

Roses are red
Violets are blue

Mount files from a WebDAV server in a local directory

If you have a server with Owncloud installed, you probably have access to it through WebDAV. You can use the command line with cadaver or mount it as a local directory with davfs2.

The Davfs article on ArchWiki is well explained:

Add yourself to the network group:

# usermod -a -G network <user>

Restart your session, and add the following entry in /etc/fstab: /home/<user>/Owncloud davfs user,noauto,uid=,file_mode=600,dir_mode=700 0 1

Edit ~/.davfs/secrets and add your credentials like this:

"" "webdavuser" "webdavpassword"

Note that the double quotes are not required, but if your password contains special characters, it may help.

Now you should be able to mount and unmount ~/Owncloud with:

$ mount ~/Owncloud
$ fusermount -u ~/Owncloud

Create a self-signed certificate in one line

$ openssl req -nodes -x509 -newkey rsa:4096 -sha512 -days 365 -keyout cert.key -out cert.pem

Let's analyze this command:

  • req: request a new PKCS#10 certificate;
  • -nodes: do not encrypt the private key (no password);
  • -x509: output a self-signed certificate instead of a certificate request;
  • -newkey rsa:4096: create a new certificate request and a new 4096 bits RSA key;
  • -sha512: sign the request with a SHA-512 message digest;
  • -days 365: the certificate will be valid for 365 days;
  • -keyout cert.key: private key filename;
  • -out cert.pem: output filename.

Use Netctl to configure Android USB Tethering

Create a file named usb-tether in /etc/netctl/ and add the following configuration :

Description="A basic dhcp ethernet connection"

Start the profile with:

# netctl start usb-tether

Or enable it with:

# netctl enable usb-tether