My Little Corner of the Net

Printing Pi

A while back I somewhat impulsively bought a floor-model Brother MFC-7365DN printer that was on the clearance shelf at Sam’s. I say somewhat impulsively because I needed a new printer and I was seriously looking at the Brother MFC line, but I didn’t know much about this particular model and hadn’t, prior to walking in to the store at least, completely decided on that line of printers. The deal was too good to pass up, though, and the printer has proven itself to be a great decision.

One thing that I didn’t know (and didn’t really think much about) going in to the purchase was Linux support. As it turns out, Brother has great support for Linux, but only for x86 devices. Their Linux drivers are proprietary and they don’t offer source code, so printing from ARM devices is a challenge. There’s plenty of discussion online about which Gutenprint drivers usually work (none of the recommendations I found did more than push out blank sheets of paper, if anything at all) or using QEMU to emulate x86 hardware (which was reported as being extremely slow, so I didn’t try).

I generally don’t print from my Raspberry Pis directly but, as we use Google Apps more and more at work, I find myself wanting to use Cloud Print more often. I really wanted, as I had done in the past, to set one of my Pis up as a Cloud Print server, but without a printer driver that wasn’t happening. In fact, I even went so far as to create an extremely kludgy, but surprisingly effective, solution that involved an always available SSH connection (using autossh and a dynamic DNS service) between a remote x86-based VPS and one of my Pis, with a tunnel to my printer. I then set up Cloud Print on the server and used that as my workaround…until this weekend.

I was browsing some packages in Synaptic when I decided to search for “Brother.” I’m pretty sure I did this before with no luck, but this time I found a package, printer-driver-brlaser, described as a “CUPS filter driver supporting (some) Brother laser printers,” which appears to be distributed by the OpenPrinting project. One of the printers listed was model DCP-7065DN–a model number quite similar looking to my printer’s. On checking out the specs for the DCP-7065DN, it looked to be pretty much the same printer as mine, less the fax capabilities, so I installed the package, set up the printer in CUPS (which was already installed from previous attempts), selecting “Brother” as the make and “Brother DCP-7065DN” as the model, and printed a test page…successfully!

I should note that CUPS found my printer automatically, but I couldn’t get it to work from the discovered selection. Instead, I manually configured the printer’s address to be socket://IP_ADDRESS:9100 (with my printer’s IP address in place of IP_ADDRESS, obviously). The driver didn’t give me an option to set the duplex default, so I’m not sure if I’ll be able to do two-sided printing or not (I haven’t tried printing anything longer than one page yet), but it’s not the end of the world if I can’t.

Just thought I’d share my experience as there seem to be lots of frustrated Brother owners trying to print from ARM devices out there.

Single Site Browser on the Raspberry Pi

Sometimes it’s easier to work with a standalone app than it is to use a web app in a browser—if you want to keep an eye open for to a chat or you need to copy and paste from other windows, it’s much easier to do if the app isn’t buried in a million tabs. Unfortunately on Linux, and especially on non-x86-based Linux, there are often no native apps available for the services I want to use.

I’ve been a fan of single site browsers—tools that let you wrap a web application into a stand alone desktop application—for quite some time. I originally discovered (and blogged about) Mozilla Prism back in the early 2000’s (though back then, I didn’t really see the point). Later, when I switched over to Macs and OSX, I found Fluid, which has been helpful for a handful of tools I use.

Nowadays, with Raspberry Pis being a hobby, I was looking for something similar. Even though a lot of the apps I use everyday are starting to support Linux, many do not distribute versions that are compatible with the PI’s ARM processor. Having a SSB for the Pi would be great…and, as it turns out, it can be done!

Peppermint is a Debian-based OS that features an SSB tool, called Ice, as one of it’s main selling features. Unfortunately, Peppermint won’t run on the Pi, but I figured with a little work, I might be able to get Ice to run. Ice, it turns out, is just a Python app, so it wasn’t difficult at all…I just needed to figure out a bunch of dependencies.

Ice requires Python 3, which I believe was installed by default on Raspbian Jessie (it’s been a while since I’ve rebuilt, but I don’t remember installing it manually). You will, however, need to install Glade and PyOpenGL and, if you don’t already have it, the git client:

sudo apt-get install glade python3-gl git

You’ll also need either the Google Chrome, Chromium, or Firefox browser, none of which are available on the Pi. Ice is, however, able to find Iceweasel, the rebranded version of Firefox that is available on the Pi, so you’ll want to have that installed, too:

sudo apt-get install iceweasel

While Iceweasel works, Chromium seems to be a little more flexible, so you might want to consider adding it as well. I’ve found kusti8‘s unofficial Chromium repo to be helpful in this regard.

You’ll also need to be sure you have the Python request package, which you can install with pip:

sudo pip3 install request

Now you’re ready to clone Ice from GitHub and copy it into place in the file system:

git clone
cd ice
cp -r . /

Once this is done, you should see an Ice icon in the Internet section of the RPi Main Menu:

Ice icon in the programs menu

Ice icon in the Raspberry Pi programs menu.

Running Ice brings up it’s configuration Window. Here I’m creating an app to access my work Slack:

Configuring Ice to run Slack as an application.

Configuring Ice to run Slack as an application.

When you click “Apply,” a new icon is added to the menu group you selected in the “Where in the menu?” field:

Slack in the programs menu

An icon for Slack has been added to the programs menu.

Running my new Slack app brings me to the Slack login page. Just a quick login and I’ll be collaborating with my team!

Slack login page in the Ice App

The newly created Ice app showing the Slack login page.

Let’s Encrypt on VestaCP with CentOS 6

As promised a couple weeks ago, I’ve created a tutorial for automatically installing Let’s Encrypt certificates on Vesta-hosted sites. As you’ll recall, Let’s Encrypt is a new certificate authority that issues free domain validated (DV) certificates to virtually anyone in an effort to make the web more secure. Let’s Encrypt tries to be fully automated, meaning that you just run a single command on your server and it takes care of all the details of certificate issuance for you: creating the private key and CSR, validating domain ownership, generating the cert, and even configuring the web server for you. Unfortunately, Let’s Encrypt and Vesta use different naming conventions for the certificate files, so the two don’t automatically play nice together. Thanks to Vesta’s command-line tools, however, it is pretty easy to bridge the gap.

TL;DR version: I’ve created a tool, which I’ve posted to GitHub, that automates the Let’s Encrypt certificate request and installation process using the Let’s Encrypt ACME client and Vesta’s command-line tools. You can clone my repository and get to work installing certs on your server.

You should be logged in a root to complete this tutorial.

Installing Python

The Let’s Encrypt client requires Python 2.7, which is not installed on CentOS 6.x by default. Simply upgrading Python will break several system utilities, like yum, so we’ll start by installing Python 2.7 in an alternate location. If you’re using a distro that includes Python 2.7, or you already have it installed, you can skip the next several steps.

First, using yum, install the development tools and libraries we’ll need to build Python from source.

yum groupinstall "Development tools"
yum install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel

Download the latest version of the Python 2.7 branch (2.7.11 as of this writing, but check and update the version numbers as appropriate.


Unzip the Python tarball and change into the newly create source directory.

tar xf Python-2.7.11.tar.xz
cd Python-2.7.11

Run the configure script.

./configure --prefix=/usr/local --enable-shared LDFLAGS="-Wl,-rpath /usr/local/lib"

Build and install the new Python binary. We’re using “make altinstall” to ensure that we don’t interfere the system version of Python (this will name the binary python2.7 instead of just python).

make altinstall

Thanks to Daniel Eriksson and Too Much Data for the How to install Python 2.7 and Python 3.3 on CentOS 6 post, from which my instructions are based. I highly recommend reading that post, as my instructions are only the bare minimum needed to get Let’s Encrypt working.

Installing Let’s Encrypt

Now that we have the right Python version, we can install the Let’s Encrypt ACME client. We’ll also install my installation tool (which isn’t much more than a bash script).

/usr/local is intended for locally installed software (as opposed to system provided software), so it seems like a good place to put these tools.

cd /usr/local

Clone the official Let’s Encrypt ACME client and my installation tool from GitHub.

git clone
git clone

Let’s Encrypt’s ACME protocol validates that a requestor is in control of the domain by checking for a the availability of a randomly named file over the web. It can do this with its own web server, or it can write a file to the web root of an existing server running on the domain. Since a Vesta server has many different web roots, we’ll tell let’s Encrypt to write these files to a central location and then configure Apache to look there for incoming validation requests.

First, create the Let’s Encrypt webroot directory.

mkdir -p /etc/letsencrypt/webroot

Then create a symlink to the letsencrypt.conf file in my GitHub repository.

ln -s /usr/local/letsencrypt-vesta/letsencrypt.conf /etc/httpd/conf.d/letsencrypt.conf

Finally, restart Apache so it will pick up the configuration change.

service httpd restart

Everything is installed and ready to go, but to run the tools you’ll need to specify the full path to them, since they aren’t in your PATH environment variable. A quick fix for this is to symlink them inside /usr/local/bin.

ln -s /usr/local/letsencrypt/letsencrypt-auto /usr/local/bin/letsencrypt-auto
ln -s /usr/local/letsencrypt-vesta/letsencrypt-vesta /usr/local/bin/letsencrypt-vesta

Now you’re ready to create your first certificate.

lets encrypt-vesta USERNAME DOMAIN

Substitute a valid Vesta user account for USERNAME and a domain hosted on that account for DOMAIN. The script will look up the account and pull the email address listed with it, using that as the contact email used in the certificate request. It will also look up the list of domain aliases associated with the domain and will include all of them as subject alternate names (SANs) in the certificate request. SANs function as additional domains on the certificate—each one will be recognized as a trusted domain by users’ browsers.

The first time you run the script, the Let’s Encrypt client will do some setup work, so it may take a minute or two. Future runs won’t require this additional work and should complete faster.

The certificates issued by Let’s Encrypt are valid for 90 days. It is recommended that you renew them every 60 days to allow ample time to mitigate issues such as reissue errors and service interruptions that might occur and leave you with an expired cert.

Let’s Encrypt is currently in beta and, while they offer unlimited certificates, they do have some limitations in place, at least for now. As of this writing, they currently only allow ten certificate requests from a single IP address over a three hour period and limit the number of SANs on a single certificate to 100. There are also limits on the number of unverified requests (as in a certificate is requested, but the validation process fails) allowed, though this shouldn’t be ann issue for most admins. These numbers may change over time as the technology platform matures and demand for the service adjusts.

Let’s Encrypt Enters Public Beta

There’s a new certificate authority in town, and it seeks to change the way we think about web security.  Let’s Encrypt, run by the Internet Security Research Group (ISRG), a partnership of a number of Internet-focused organizations like the EFF, Mozilla, Cisco, Akamai, and now Facebook, entered it’s public beta period this week and is now issuing free, browser-trusted, domain-validated certificates to pretty much anyone who wants them.

I’ve been doing some experimenting with Let’s Encrypt for the past few weeks as part of their limited beta program, and this post was already in the works.  In fact, I hadn’t even noticed that they entered public beta until after I started writing it and I went to the site to check on some facts.

While there are many issues with the SSL/TLS current certificate issuing paradigm, Let’s Encrypt aims to solve one of the biggest: the barrier of entry.  Not only are Let’s Encrypt certificates free, the process for getting them is mostly automatic.  To contrast, I’ve used another CA to obtain free certs for a number of my personal sites and projects for years and the process to get them is not easy.  With that CA, getting a new certificate is a multi-step process which includes  creating a client certificate to authenticate with the service, installing it in my browser, multiple steps of verifying domains, email addresses, and the like, waiting for verification codes to come by email, and manually installing the certificates when they arrive.  That CA also doesn’t support Subject Alternative Names (SANs), so I’m limited to creating certificates that work with only one domain or subdomain.

With Let’s Encrypt, after a little bit of one-time server setup, I just run a single shell command and it does everything for me (or most of it, anyway).

Before going too much further, let’s point out that Let’s Encrypt is not for everyone.  The certificates they issue are domain validated (DV), which means that the only validation done is a check that the server responding to the domain name in the certificate request is actually aware of the request.  As such, there is no information about the organization running the site in the certificate, so while a Let’s Encrypt certificate will enable encrypted connections in browsers (and without any certificate warnings), it does nothing to validate that the server is actually being run by the entity that it purports to be.  Because of this, Let’s Encrypt certificates probably shouldn’t be used for most production-level sites, especially not for ecommerce, banking, or anything else where a high level of trust is required.  On the other hand, they could be perfect for dev and test servers, where the level of trust is not required.

Let’s Encrypt intends their certificates to be used on the millions of personal sites, running software like WordPress, where site owners log in in the clear.  These kinds of sites are often the targets of hackers because (among many other reasons) people tend to reuse passwords, so the clear-text passwords they use to log in to their blog may very well be the same ones they use to log in to their banks.

So how does automatic certificate generation work?  In a nutshell, you install the Let’s Encrypt software on your server and use it to request a certificate for one or more domains.  The client uses the Automated Certificate Management Environment (ACME) protocol, which was developed by Let’s Encrypt, to submit the certificate request (which it generates for you), to the Let’s Encrypt CA.  The CA then verifies the request by checking that a specific file, created by the client software, is available from your server via the web.  If the server finds the file, the domain is verified, and the certificate is issued.  If the request is made for a domain that isn’t hosted by the server, the verification will fail, and the certificate won’t be issued.  When the certificate is issued, it’s returned to the client which will save it to the server and, depending on the implementation, may also reconfigure the web server to use the certificate for the domains it covers.

Let’s Encrypt supports SANs on it’s certificates, so it’s possible, for instance, to cover the .com, .net, and .org variants of a given domain, with both and without www prefixes, in a single cert (this isn’t even possible with the most commercial CAs, unless you are willing to pay for it—SANs are usually a premium feature and it usually costs almost as much to add an additional domain as it does to get a whole new cert).  Let’s Encrypt also says that wildcard certs are in the works, though there are some issues they need to work through concerning how to validate them, since a wildcard certificate would be valid for any subdomain on the domain it was issued to.  Fortunately, since there’s no limit to the number of SANs you can add or the number of certificates you can request (within reasonable limits, for now, at least), wildcard certificates would only be necessary in some very specific use cases.

The biggest downside to Let’s Encrypt is also one of it’s biggest strengths: certificates are only valid for 90 days.  This is a stark contrast to the current industry standard that allows certificates to be issued for up to two years and terms of five years or more being common not that long ago.  The 90-day limit means that site owners need to stay vigilant of expiration dates, but in a world of script-kiddie exploits and expired domains getting scooped up by domain squatters faster than you can say “connect,” it’s a pretty smart idea to recycle certificates quickly.  Plus, renewing is as easy as running the ACME client again, and an auto-renewal process is promised somewhere down the road.  For now, most monitoring tools can warn about certificate expiration in advance of it happening.  Let’s Encrypt recommends renewing every 60 days to ensure there’s plenty of time to fix any issues that may come in in the renewal process.

If a public key ever gets compromised, revoking a certificate is easy, too…it’s just another command to the Let’s Encrypt client.  Remember that other CA I mentioned?  They actually charge a fee to revoke a cert and won’t issue a new one until either the old one expires or you revoke it, even if you just lost the private key because of stupidly copying another file on top of it…not that I’ve ever done that!

I figure I’ll probably start replacing my free certs with Let’s Encrypt certs as they expire.  Using them will be a bit more work in that I’ll have to remember to renew them every couple months, but I’m working on a script that handles the installation process with Vesta, where most of my sites reside, so the process will be much, much easier than it is now.  I plan to release this script once I’ve done some thorough testing, so watch for another post in the next week or so.

Using Mailman with Vesta on CentOS 6

About a year and a half ago I moved several of my websites to a server running the VestaCP control panel. At the time I posted an extensive review of the software, including a footnote that I found a way to get Mailman working with it. Apparently this is something a lot of people want to do because, since then, several people have contacted me looking for instructions. Life’s been hectic, and this wasn’t on the top of my mind, but I promised that I’d write a post. So, better late than never, here it is…

This tutorial will help you get GNU Mailman 2.1.x running on a Linux server. It’s geared toward CentOS 6.x, but will probably work with other Linux distros, although some file paths may change. It also assumes a standard VestaCP installation, with both Apache and Nginx running on the server.

Before we get too far into this, I should also point out that this tutorial only gets Mailman running on a Vesta server, it does not integrate it with the Vesta web interface. This means that you, as root, will need to set up new lists on the command line—you won’t be able to let your users create their own lists. Once a list is set up, however, it can be completely administered through the Mailman web interface, so hopefully this won’t be too big a deal for most situations.

Mailman is available as a CentOS package, but at the time that I did the install, several of the big email providers had recently made changes to their DMARC policies that broke older versions of Mailman, so I chose to build from the then-latest source release, which included new workarounds to address the issue.

When I did my installation, Mailman 3 was in beta, I believe, but since it wasn’t yet stable, and since I was moving existing Mailman 2.1 lists, I chose to stick with 2.1. Mailman 3 is now generally available and has some interesting new features, but it’s a significantly different beast and this tutorial probably won’t be helpful if you want to jump to 3.0. Fortunately, Mailman 2.1 is still in support and is still receiving regular updates, with 2.1.20 being the most recent version as of this writing.

This tutorial assumes that System V is used to manage services on the server. Many new Linux distros, including CentOS 7, have switched to systemd, which uses a different configuration format. If you’re using a systemd-based distribution, you’re on your own to figure that out, as I haven’t tried to do it yet (with Mailman anyway) myself.

There are four main parts to getting Mailman running on Vesta:

  • Install Prerequisites
  • Building and installing Mailman
  • Configuring Exim
  • Configuring Apache and Nginx
  • Creating your lists

While Mailman 2.1 supports multiple domain names, it does not allow the same list name to be used multiple times on different domains. In other words, if you host and on the same server, and you create a list, you can’t also create a list. While this wasn’t really a deal breaker for me, I decided to come up with a work around anyway, taking my lead from cPanel. cPanel appends the domain name onto the list name (i.e., but somehow strips it out in the email messages so that users only see I couldn’t figure out exactly how cPanel does this, but I came up with a pretty good facsimile by using Exim’s address rewriting features.

Installing Prerequisites

Mailman requires that the a C compiler and the Python development libraries be installed on the server, which are not installed by default. In addition, the pip command is required to install the dnspython Python module:

yum install -y gcc gcc-c++ python-devel python-pip

Now run the following command to install dnspython:

pip install dnspython

Building Mailman

We’ll start by downloading the latest release of the 2.1 branch of Mailman (2.1.20 as of this writing). Check to be sure you’re using the latest version.


Unzip the package

tar xvzf mailman-2.1.20.tgz

Switch to the directory that was created when the package was unzipped

cd ./mailman-2.1.20

Create the account and group that Mailman will run under:

useradd -r mailman

Mailman expects that the directories it will be installed into exist when you start the installation. Create those directories and set the ownership and permissions that Mailman requires:

mkdir -p /usr/local/mailman /var/mailman
chown mailman.mailman /usr/local/mailman /var/mailman
chmod 02775 /usr/local/mailman /var/mailman

Run the configure script to ensure all necessary libraries are available and to get Mailman ready to build:

./configure --prefix=/usr/local/mailman --with-var-prefix=/var/mailman --with-mail-gid=mailman --with-cgi-gid=apache

Build the package:


And install it:

make install

Run the check_perms script to ensure that permissions are as Mailman expects them to be:


If the above script returns any errors (and it probably will), run it again with the -f flag to have it try to fix the errors. In some cases, you may need to do this a few times before everything works.

/usr/local/mailman/bin/check_perms -f

Copy the /var/mailman/scripts/mailman file to /etc/init.d. This is the script that will be used to start and stop the mailman service:

cp /usr/local/mailman/scripts/mailman /etc/init.d

Copy the mailman crontab file to /etc/cron.d so that Mailman’s periodic tasks, such as sending out email reminders of posts awaiting moderation and managing the list archive, are run regularly.

cp /usr/local/mailman/cron/ /etc/cron.d/mailman

Now set a default password for Mailman. This password can be used in place of any list’s administrator password, so be sure to select a strong password.


Mailman requires that a default list, aptly named “mailman,” be created. You’ll be prompted for an administrator email address and a list password when you run this command. You can ignore the list of aliases that is displayed when you run the command.

/usr/local/mailman/bin/newlist mailman

Configure the system so that Mailman is automatically started when the server boots up:

chkconfig mailman on

And start the mailman service:

service mailman start

Mailman writes its log files to /var/mailman/logs. To be more consistent with other services, thereby making the logs easier to find, symlink Mailman’s logs directory in /var/log:

ln -s /var/mailman/logs /var/log/mailman

You’ll also want to rotate these logs on a regular basis so that they don’t get too big. To do this, create a new log rotate script. (Note: I prefer vi and use it in any instructions that require editing files in this tutorial, but feel free to substitute nano or your favorite editor if you don’t know vi or prefer something else.)

vi /etc/logrotate.d/mailman

Add the following contents to that file:

/var/log/mailman/bounce /var/log/mailman/digest /var/log/mailman/error /var/log/mailman/post /var/log/mailman/smtp /var/log/mailman/smtp-failure /var/log/mailman/qrunner /var/log/mailman/locks /var/log/mailman/fromusenet /var/log/mailman/subscribe /var/log/mailman/vette {
  /usr/local/mailman/bin/mailmanctl reopen >/dev/null 2>&1 || true

Configuring Exim

The next step in the configuration process is to integrate it with Exim, Vesta’s mail transfer agent (MTA). This allows Vesta to properly route incoming messages sent to a Mailman list to the Mailman software to process them. Exam is actually a preferred MTA to use with Mailman because it’s ability to route messages based on directory listings means that no per-list Exim configuration is necessary. By contrast, most other MTAs require you to set up several email aliases for each list you create.

The next several steps require editing the /etc/exim/exim.conf file. To make what’s going on more understandable, I’m going to start at the bottom of the file and work my way toward the top.

First, create a backup of the conf file, just to be safe:

cp /etc/exim/exim.conf /etc/exim/exim.bak

Then open it to edit.

vi /etc/exim/exim.conf

As mentioned above, Mailman does not support lists on multiple domains that share the same name. To work around this, I decided to follow cPanel’s lead and appended the domain name to the list name in the form listtname__domain.tld__. (Note that the trailing double underscore is necessary to properly parse address that contain a command, such as “-unsubscribe.” I couldn’t get the rewrite to properly parse these addresses without the underscores there.) This creates list email addresses that look like listname__domain.tld__@domain.tld which is undesireable. When mail is sent, however, Exim rewrites the email addresses it finds in the message into the preferred form, listtname@domain.tld, so end users see the cleaner form of the address.

Jump to the bottom of the file and find a line that starts with “begin rewrites” and add the following after that line:

#messages generated by Mailman will have the format of list__domain__@domain
#this rule will rewite them to list@domain before they are delivered
^([a-z0-9-\.]+)__[a-z0-9-\.]+__(-[a-z0-9]+)?@(.*) $1$2@$3 SEh

The above rewrite rule will strip the extraneous domain name from the list name when messages are sent, but only when the address appears in specific email headers. While this ensures that the to, from, and cc headers, for example are rewritten, it does not rewrite some of the more obscure headers, such as those that instruct mail clients how to handle unsubscribe requests. For this reason, we include two transports and two routers, the next features we’ll configure, that will properly route inbound messages that use either address format.

Transports that tell Exim how to handle a given incoming message. In this case, the transports instruct Exim to open a pipe to Mailman, when it receives a message associated with a list, through which it will pass the contents of the message, allowing Mailman to take over the processing.

Find the line “begin transports,” and add the following lines after this line but before the “begin rewrites” line.  There are several other transports already defined.  Where you put these doesn’t matter, as long as they’re in the “transports” section of the file.

  driver = pipe
  command = /usr/local/mailman/mail/mailman \
    '${if def:local_part_suffix \
    {${sg{$local_part_suffix}{-(\\w+)(\\+.*)?}{\$1}}} \
    {post}}' \
  current_directory = /usr/local/mailman
  home_directory = /usr/local/mailman
  user = mailman
  group = mailman
  driver = pipe
  command = /usr/local/mailman/mail/mailman \
    '${if def:local_part_suffix \
    {${sg{$local_part_suffix}{-(\\w+)(\\+.*)?}{\$1}}} \
    {post}}' \
  current_directory = /usr/local/mailman
  home_directory = /usr/local/mailman
  user = mailman
  group = mailman

Finally, we create two routers. These tell Exim where to look to determine whether an incoming message has a valid destination on the server and, when a match is found, which transport it should be routed to for processing.

Add the following lines to the file between “begin routers” and “begin transports.”  Like with the transports, positioning doesn’t matter, as long as both definitions appear before the “begin transports line of the file.

  driver = accept
  require_files = /usr/local/mailman/mail/mailman : \
  local_part_suffix = -admin : \
    -bounces : -bounces+* : \
    -confirm : -confirm+* : \
    -join : \
    -leave : \
    -owner : \
    -request : \
    -subscribe : \
  transport = mailman_transport
  driver = accept
  require_files = /usr/local/mailman/mail/mailman : \
  local_part_suffix = -admin : \
    -bounces : -bounces+* : \
    -confirm : -confirm+* : \
    -join : \
    -leave : \
    -owner : \
    -request : \
    -subscribe : \
  transport = mailman_transport_norewrite

Save the changes to the file and restart exim to enable them:

service exim restart

Configuring Apache

Configuring Apache was a bit of a challenge because Mailman is very specific about which user account it runs under. Vesta, on the other hand, uses suexec to run all scripts under the UID of the site owner, which breaks mailman. The solution is to run Mailman on a different port, where it is not bound by suexec’s rules. Later, we’ll set up a proxy in Nginx so that users won’t need to remember complicated URLs to manage their lists.

Mailman’s web-based admin tool has several small images at the bottom, which it expects to find in Apache’s /var/www/icons directory. Create symlinks in the icons directory that point at these images.

cp -s /usr/local/mailman/icons/* /var/www/icons

Create a new configuration file for Mailman’s Apache configuration.

vi /etc/httpd/conf.d/mailman.conf

Add a listen directive at the very top of this file. This tells Apache to bind to port 8090 when it starts and to listen for HTTP connections on this port.

Listen 8090

Next, add a VirtualHost block to handle requests coming in to this port.

<VirtualHost *:8090>
  ScriptAlias /mailman/ /usr/local/mailman/cgi-bin/
  AllowOverride None
  Options ExecCGI
  Order allow,deny
  Allow from all

  Alias /pipermail/ /usr/local/mailman/archives/public/
  Options Indexes MultiViews FollowSymLinks
  AllowOverride None
  Order allow,deny
  Allow from all

Save the file and restart Apache:

service httpd restart

Now, depending on your firewall settings, you may be able to access the Mailman web interface at http://domain.tld:8090/mailman/listinfo. If you can’t, don’t worry about it as we’ll set that up next.

Configuring Nginx

Next, Nginx needs to be configured on each site to proxy requests to Mailman’s Apache listener on port 8090. Fortunately, we only need to apply this change to a few template files. Vesta offers a tool to reapply the template to a site’s configuration, which is a big help if you already have a lot of sites configured on the server.

Vesta stores it’s Nginx configuration templates in /usr/local/vesta/data/templates/web/nginx. This directory contains templates for each of the “proxy templates” you can choose when setting up a hosting package. Files with the extension .tpl are for HTTP configurations and .stpl files are for HTTPS configurations. You’ll need make the follow edits to each of the templates in the directory (or at least each of the templates that you use on your server).

We’ll start with HTTPS. Mailman, for the most part, uses relative URLs for most of it’s interface, so it runs fine on both HTTP and HTTPS. The user administration pages, however, use absolute URLs, so when managing users you can be dropped to HTTP unexpectedly. While not a standard module of Nginx, the CentOS builds include the ngx_http_sub_module, which can do string substitutions on page output. We can use this to rewrite the HTTP URLs in Mailman’s output to HTTPS to avoid problems.

Open the hosting.stpl file in the directory noted above:

vi /usr/local/vesta/data/templates/web/nginx/hosting.stpl

Between the end of the block that begins “location /error/“ and before the beginning of the block that starts “location @fallback” add the following:

location ~ ^/((mailman|pipermail)/?.*)$ {
  sub_filter http://%domain_idn% https://%domain_idn%;
  sub_filter_once off;
location /icons/ {
  alias /var/www/icons/;

Save the file and do the same for the other .stpl files in the directory.

There are two options for the HTTP configuration. If you know that all of your sites will have SSL certificates, as mine do, you can use the following configuration to direct all HTTP requests to their HTTPS counterparts. I recommend this approach if you can support it.

Open the hosting.tpl file:

vi /usr/local/vesta/data/templates/web/nginx/hosting.tpl

Again, between the “location /error/“ and “location @fallback” blocks, add the following:

location ~ ^/((mailman|pipermail)/?.*)$ {
  rewrite ^(.*)$ https://$host$1;
location /icons/ {
  alias /var/www/icons/;

And, like before, repeat the change on the other .tpl files as well.

If you can’t rely on all of your sites having SSL available, you can instead use a variation on the HTTPS configuration that doesn’t include the HTTP-to-HTTPS conversion. Follow the same instructions for the above HTTP changes, but add the following instead:

location ~ ^/((mailman|pipermail)/?.*)$ {
location /icons/ {
  alias /var/www/icons/;

Now that the templates are updated, we need to apply them to each existing site. To do this, run the following command for each hosting user account (not domain) on the server:

/usr/local/vesta/bin/v-rebuild-web-domains username

Once all accounts have been updated, restart Nginx to make the changes take effect:

service nginx restart

Creating Lists

Mailman is now up and running. All that’s left to do is to start creating lists.

When setting up a new list, remember that it is necessary to use the full address syntax, listname__domain.tld__@domain.tld for the list address. You should also specify both the emailhost and urlhost options to ensure that the list is configured correctly.

/usr/local/mailman/bin/newlist —emailhost=domain.tld —urlhost=www.domain.tld listname__domain.tld__@domain.tld

You’ll be prompted for an email address of the list administrator and for a list password. When the list is set up, you can access it at http://www.domain.tld/mailman/admin/listname__domain.tld__.

Since, through the Exim rewrites, we’re running the list from a different address than we configured Mailman to use, it is necessary to make one small settings change to the list’s settings. Open the list’s administrative page in a browser and log in with the password you provided in the previous step.

Once logged in, click on “Privacy Options” in the “Configuration Categories” menu. Then click on “Recipient Filters.”

On the Recipient Filters page, find the field labelled “Alias names (regexps) which qualify as explicit to or cc destination names for this list” and add the preferred list address (listtname@domain.tld). Without this, Mailman will not recognize the preferred address as being a valid list address and will hold any messages that are sent using it for moderation.

Click the “Submit Your Changes” button to save the change. Then make any additional settings changes you require and add recipients to the list under “Membership Management” in the “Configuration Categories” menu. You’re list is now ready to use.