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 cats.com and dogs.com on the same server, and you create a customers@cats.com list, you can’t also create a customers@dogs.com 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. customers_cats.com), but somehow strips it out in the email messages so that users only see customers@cats.com. 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 http://launchpad.net/mailman to be sure you’re using the latest version.
wget https://launchpad.net/mailman/2.1/2.1.20/+download/mailman-2.1.20.tgz
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:
make
And install it:
make install
Run the check_perms script to ensure that permissions are as Mailman expects them to be:
/usr/local/mailman/bin/check_perms
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/crontab.in /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.
/usr/local/mailman/bin/mmsitepass
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 { missingok sharedscripts postrotate /usr/local/mailman/bin/mailmanctl reopen >/dev/null 2>&1 || true endscript }
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.
mailman_transport: driver = pipe command = /usr/local/mailman/mail/mailman \ '${if def:local_part_suffix \ {${sg{$local_part_suffix}{-(\\w+)(\\+.*)?}{\$1}}} \ {post}}' \ ${lc:$local_part}__${lc:$domain}__ current_directory = /usr/local/mailman home_directory = /usr/local/mailman user = mailman group = mailman
mailman_transport_norewrite: driver = pipe command = /usr/local/mailman/mail/mailman \ '${if def:local_part_suffix \ {${sg{$local_part_suffix}{-(\\w+)(\\+.*)?}{\$1}}} \ {post}}' \ ${lc:$local_part} 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.
mailman_router: driver = accept require_files = /usr/local/mailman/mail/mailman : \ /var/mailman/lists/${lc::$local_part}__${lc::$domain}__/config.pck local_part_suffix_optional local_part_suffix = -admin : \ -bounces : -bounces+* : \ -confirm : -confirm+* : \ -join : \ -leave : \ -owner : \ -request : \ -subscribe : \ -unsubscribe transport = mailman_transport
mailman_router_norewrite: driver = accept require_files = /usr/local/mailman/mail/mailman : \ /var/mailman/lists/${lc::$local_part}/config.pck local_part_suffix_optional local_part_suffix = -admin : \ -bounces : -bounces+* : \ -confirm : -confirm+* : \ -join : \ -leave : \ -owner : \ -request : \ -subscribe : \ -unsubscribe 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 </VirtualHost>
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)/?.*)$ { proxy_pass http://127.0.0.1:8090/$1$is_args$args; 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)/?.*)$ { proxy_pass http://127.0.0.1:8090/$1$is_args$args; }
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.
Hi
Thank you for the instructions. I’ve tried them on CentOS7 (saw your comment about systemd, but hoped you might lead me to the right direction)
I could follow your precise instructions with those exceptions:
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
I also hafd to add ‘bin’ for the rebuild:
/usr/local/vesta/bin/v-rebuild-web-domains username
Everything seems to run, but I cannot access the mailman interface on :8090
I’ve added 8090 to allow in the firewall
Could you give me a hint where to look for an error message?
Tnx!
Daniel,
I don’t see anything in what you’ve posted that would prevent Apache from responding on port 8090. Did you include the “Listen” directive and wrap everything in the block? (That’s not clear from what you posted.)
You can check what ports Apache is listening on with the following command (run it as root):
lsof -i | grep httpd
If it’s working correctly, you should see output similar to
TCP *:8090 (LISTEN)
for each of your open Apache processes. If you see that, you should be able to find errors, if there are any, in the main Apache error log (/var/log/httpd/error_log) or possibly in the error log for a site you’re trying to run Mailman on (/home/USER/web/DOMAIN/logs/DOMAIN.error.log).If everything is configured correctly but Apache can’t bind to port 8090 for some reason (something else is using it), I would suspect it would report this when you start Apache. If not try running tail in a separate window while you restart Apache in another:
tail -f /var/log/httpd/error_log
That should show you anything abnormal that’s happening when Apache first starts up.
If you can’t find anything in the Apache log, check the main system log at /var/log/messages.
As for the v-rebuild-web-domains command, that’s a typo on my part. You are correct–the path is /usr/local/vesta/bin/v-rebuild-web-domains. I’ll update the post.
I have exactly the same problem with Daniel (running Ubuntu 14.04). Getting error that “allow is not allowed in here”. Any ideas?
Hi,
I’m getting the following error from exim:
2016-01-01 07:59:50 H=localhost ([ip]) [::1] F= rejected RCPT : relay not permitted
2016-01-01 08:01:45 H=localhost ([ip]) [::1] sender verify defer for : remote host address is the local host
Do you know how to fix this?
I suspect what’s happening is that you are attempting to send mail that’s coming from list members that don’t have an email address that’s served by the server, so Exim is rejecting the messages Mailman is trying to send (since they’re probably marked as From: the original sender). I think I ran into this early on, too, though I apparently forgot about it when I finally wrote the article.
I have the following at teh top of my /etc/exim/exim.conf file, right after the “domainlist relay_to_domains” line:
hostlist relay_from_hosts = 127.0.0.1
I’m far from being an Exim expert, but I believe I ended up adding that based on some searching I did. If I’m understanding it correctly, it’s telling Exim to accept all messages that originate from the local machine, even if they appear to be coming from a domain Exim wouldn’t normally handle.
The “allow is not allowed here” error is usually caused by not having “AllowOverride Limit” defined in your httpd.conf. I’d start by checking that.