Webmail using Roundcube

This feature is completely optional. If you are eager to get finished then skip this page and maybe come back later. You can still access your mail server using a mail user agent like Thunderbird.

Power users may still want to use a mail client like Thunderbird. But most users nowadays seem to prefer reading their emails in the web browser. Let us install a web application for that purpose: Roundcube.

Installation

Start by installing the software packages:

sudo apt install -y roundcube roundcube-plugins roundcube-plugins-extra roundcube-mysql

Replace roundcube-mysql with roundcube-pgsql if you are using PostgreSQL. Roundcube stores user settings in the database. So, you will get asked to set up database access.

Choose Yes.

When asked for a password just press ENTER.

Configure Apache

Do you remember that earlier in this guide I asked you how want to name your mail server? Whether you want to use one common name like “webmail.example.org” for all your domains? Or if you prefer different host names for each domain like “webmail.domain1.com” and “webmail.domain2.com”? If you want to use just more then you will have to create one virtual host configuration per domain. The following instructions will just deal with one common host name.

To get Apache to serve the Roundcube application you need to edit the /etc/apache2/sites-available/webmail.example.org-https.conf file. I suggest you change the DocumentRoot line to:

DocumentRoot /var/lib/roundcube/public_html

All URLs are relative to that directory. So, if you go to https://webmail.example.com/ then files are looked up in that directory.

Also add this line within the same VirtualHost section to add a couple of prepared security settings:

Include /etc/roundcube/apache.conf

And as usual Apache needs to be restarted after the configuration change:

sudo systemctl restart apache2

Check that Apache is running properly:

systemctl status apache2

In case of a problem run “sudo apache2ctl configtest” to find the cause.

Limit access to localhost

The main configuration file of Roundcube is located at /etc/roundcube/config.inc.php. Feel free to customize the file. Fortunately, nowadays the basic settings are already as we need them. However, these two settings need to be changed by you:

$config['imap_host'] = 'tls://mail.example.org:143';
$config['smtp_host'] = 'tls://mail.example.org:587';

For Roundcube on Debian 11, you need to configure default host and smtp server.

$config['default_host'] = ‘’;
$config['smtp_server'] = 'localhost';

For example, I changed them to:

$config[‘default_host’] = ‘localhost’;
$config[‘smtp_server’] = “tls://webmail.umd.me.uk”;

Be default the login screen provides a text box where you need to enter the IMAP host which you want to connect. This box is hidden by setting the default_host to be localhost.

Restart apache2 server. Now when your users enter https://webmail.example.org/ in their browser they should get the Roundcube login form.

Keep in mind that we are using the email address as the account name of the user. So, when logging in please enter the email address as the user name. E.g., ‘john@example.org’ and password ‘summersun’.

Plugins

Roundcube comes with various plugins that you can offer your users. I recommend at least these two: Again, edit the /etc/roundcube/config.inc.php file and look for the plugins configuration. To enable the recommended plugins change it to:
        $config['plugins'] = array(
            'managesieve',
            'password'
        );
        

password plugin

Plugins are configured through files located in the /etc/roundcube/plugins directory. Let’s begin with the password plugin. Edit the /etc/roundcube/plugins/password/config.inc.php file.

Oops, that file looks pretty empty. But it refers us to an example file at /usr/share/roundcube/plugins/password/config.inc.php.dist. There are many different methods to let users change their passwords. As we store that information in the SQL database, that is the part we need to set up.

Remove the empty definition line of $config from your config.inc.php file. Let’s go through the required settings one by one: Make sure that this config file is not world-readable:

sudo chown root:www-data /etc/roundcube/plugins/password/config.inc.php
sudo chmod u=rw,g=r,o= /etc/roundcube/plugins/password/config.inc.php

Try it. Log into Roundcube as john@example.org with password ‘summersun’. Go to the Settings. Choose Password. Enter a new password twice. You should get a success message at the bottom right. Now logout and login with the new password. Does it work? Great.

sieve plugin

Sieve is a simple programming language to be used for server-side rules. Dovecot executes these rules every time a new email comes in. There are global rules that are executed for every email. And of course, every user/mailbox can have its own rules. To manage sieve rules Dovecot offers the managesieve interface that you enabled earlier. So, we just need to tell Roundcube how to access it.

The configuration file for Roundcube’s managesieve plugin is found at /etc/roundcube/plugins/managesieve/config.inc.php. This time just one setting is required to tell Roundcube which server to talk to:

$config['managesieve_host'] = 'localhost';

Sieve rules are stored in a special syntax on the server. This is an example that moves all incoming emails to the test folder that have “test” in the subject:
        require ["fileinto"];
        if header :contains "subject" "test"
        {
          fileinto "INBOX/test";
        }
        
You do not need to learn this syntax though. Roundcube’s sieve rule editor is way more user-friendly.

Try adding a sieve rule for john@example.org in Roundcube. That feature is located in Settings/Filters. You will find the machine-readable sieve code at /var/vmail/example.org/john/sieve/roundcube.sieve.

The rule editor looks like this:

Roundcube Filter

Testing email delivery

So far you have spent considerable time with theory and configuration. Are you worried whether all you did actually leads to a working mail server? As the final step, let’s verify that everything you did so far works as expected.

You can get a list of all files and directories within by running:

sudo find /var/vmail

You may still get something along the lines of:

/var/vmail/umd.me.uk/sjin1239
/var/vmail/umd.me.uk/sjin1239/Maildir
/var/vmail/umd.me.uk/sjin1239/Maildir/cur
/var/vmail/umd.me.uk/sjin1239/Maildir/tmp
/var/vmail/umd.me.uk/sjin1239/Maildir/dovecot.list.index.log
/var/vmail/umd.me.uk/sjin1239/Maildir/dovecot-uidvalidity.6869d39b
/var/vmail/umd.me.uk/sjin1239/Maildir/dovecot.index.log
/var/vmail/umd.me.uk/sjin1239/Maildir/new
/var/vmail/umd.me.uk/sjin1239/Maildir/dovecot-uidlist
/var/vmail/umd.me.uk/sjin1239/Maildir/dovecot-uidvalidity

Basically, the schema you see here is /var/vmail/DOMAIN/USER/Maildir/…

Eeach IMAP mail folder has three subdirectories:

Send a test email

It is time to send a new email into the system. My favorite tool for mail tests is swaks that you installed earlier. In the original terminal run:

swaks --to sjin1239@umd.me.uk --server localhost

If everything worked as expected Postfix has accepted the email and forwarded it to Dovecot which in turn wrote the email in sjin1239’s maildir.

Look again:

sudo find /var/vmail

Dovecot has now created a directory structure for sjin1239 and created a new file:

/var/vmail/
[…]
/var/vmail/umd.me.uk/sjin1239/Maildir/new/1762718252.M298981P723801.webmail.umd.me.uk,S=731,W=751
[…]

The file will have a different name on your system – that’s okay. It is the only file in the “new” folder.

You can also use a slightly more comfortable tool to access Maildirs that will come handy for you as a mail server administrator: “mutt”.

sudo mutt -f /var/vmail/umd.me.uk/sjin1239/Maildir

What you see now are the contents of sjin1239’s mailbox:

mutt Inbox

Using mutt is a nice way to check mailboxes while you are logged in to the mail server.

To reiterate what happens when you receive an email:

  1. Postfix receives the email (using the “swaks” command in this example – but usually through the network using the SMTP protocol from other servers)
  2. Postfix talks to Dovecot via LMTP and hands over the email
  3. Dovecot writes the email file to disk

Accessing the email via IMAP (Roundcube)

Now that the email has been delivered you can talk to Dovecot using the IMAP protocol to retrieve your email again. Are you still logged in via the Roundcube webmail interface? Then just reload and you will see the email.

roundcube email content

Prevent spoofing using DKIM

Email sender spoofing is the act of pretending to be in control of someone else’s email address. This is a common problem with phishing. Often scammers send emails with a sender address of something@paypal.com and hope that the recipient falls for it and trusts them. In fact SMTP does not care which sender address you send. Many mail service providers enforce that you send emails only using your own email address. But some do not. And spammer and scammers obviously could not care less.

So over ten years ago a new method was conceived that added a cryptographic signature to the header of an email that the recipient could check to verify the authenticity of the sender and the integrity of the email. The signature is created using a private key that only the sending mail server has. It can then be verified by the recipient by downloading the corresponding public key from the DNS zone of the sending domain and running a signature check. This works very similar to PGP or S/MIME signing – just on a domain level. Your mail server can sign all outgoing emails automatically. The method used nowadays is called Domain Keys Identified Mail – or short: DKIM.

Let’s take an example. I have just sent an email from umd.edu to my personal email account on my own mail server. UMD uses google DKIM signing so the email got this additional header from umd’s mail servers:
    DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
        d=umd.edu; s=google; t=1769009380; x=1769614180; darn=umd.me.uk;
        h=to:subject:message-id:date:from:mime-version:from:to:cc:subject
         :date:message-id:reply-to;
        bh=XRSk1gOjGr6A0Uwo/26eRJlDhIZB0ADlk0G2eiYneK4=;
        b=fH633CdWduxnD3BTeYMr161bOUmpgSUa8JyG+PKsRY85GE+…
        

I need UMD’s DKIM public key to verify that signature. It is stored in their DNS zone as a TXT record of “google._domainkey.umd.edu”. The “google” is the key selector that is mentioned in the signature as “s=google”. You can use any number of keys as long as you create the signatures with the matching private key. The “_domainkey” part is the standard subdomain for DKIM keys. So let’s get that TXT record:

dig +short google._domainkey.umd.edu txt

This returns…

k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuc26OdDu/pFkYHU...

That is the public key that I can use to verify the signature. An automated check can be done using the “opendkim-testmsg” tool as described later. I can run it and paste the entire email including headers and body into it. If it doesn’t complain with an error message then the signature is correct.

Sounds good? Then let’s implement that for your email domain, too.

Creating a keypair

As explained above you need a private key that your mail server will use and a public key that gets added to your DNS zone. Rspamd can create DKIM keys already. You may want to install “dig” though which allows to query DNS records. It works similar to “nslookup” but is more versatile.

sudo apt install dnsutils

First, install Rspamd. Optionally, install Redis as a storage backend for Rspamd to store its training data about spam and ham.

sudo apt install -y rspamd redis-server

Rspamd has its built-in DKIM signing module enabled by default.

If you put your key file into /var/lib/rspamd/dkim/ using a certain naming scheme it will pick it up automatically. Create that directory to store keys in:

sudo mkdir /var/lib/rspamd/dkim
sudo chown _rspamd:_rspamd /var/lib/rspamd/dkim

Create your keypair:

rspamadm dkim_keygen -d example.org -s 20250626

The selector I chose is 20250626 because that’s the day I created it. It doesn’t matter though – you can name it anything you want. “dkim” is the default selector if you do not use maps. But you will probably someday want or need to replace the key so I recommend you rather use maps as explained further below. It gives you more flexibility and is pretty easy to do.

The output will look like this:
-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQE…
-----END PRIVATE KEY-----
20250626._domainkey IN TXT ( "v=DKIM1; k=rsa; "    "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCZ8..." ) ;
        
The first part is the private key. And that includes the “…BEGIN…” and “…END…” lines. This key must be kept secret and will only be used by your mail server to sign outgoing emails.

The second part is the DNS record you need to add to your domain’s DNS zone. Let’s start with that.

Adding the DNS record

Before you start signing your emails you must make sure that the public key is properly present in your DNS zone for the domain you are sending emails from. Otherwise, the recipient will be unable to verify the signature and may incorrectly assume that the email was spoofed.

Take a look at the TXT record. It will look something like this:

**20250626**._domainkey IN TXT ( "v=DKIM1; h=sha256; k=rsa; " "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCxenHupkYLPmFMbJjV9dQI..." ) ;

If you are running your own DNS server you should be able to copy this entire file and put it into your DNS zone. However if your internet provider offers you just a web interface to manage your domains then create a new TXT record with a host name of “20250626._domainkey” in your domain and put the string within the double-quotes into it as the value. In my example:

**20250626**._domainkey ➠"v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCc7EX3nl1VNCi6......"

Be aware that the string you got contains two strings “…” + “…” that must be merged into one to work. (The syntax with quotes is meant for a DNS zone file if you run your own name server.) Depending on your ISP it may take a while until the new record is visible on the internet. You can use dig to verify that:

dig +trace **20250626**._domainkey.example.org txt

If you get the TXT entry like as follows then you are ready to enable DKIM signing in Rspamd for that domain:
TXT "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCxen..." "" from server foo.bar in 24 ms.

Enabling DKIM maps in Rspamd

As explained above it is advised to use DKIM maps. It’s nothing fancy. Just a simple file defining which selector you want to use for a certain domain. Rspamd will assume that your selector is always “dkim” unless specified otherwise in a map. If you used “dkim” then you may get into trouble when you later want to replace your key. DNS is a sluggish system and propagating a new DKIM public key may take a day. Emails signed with a newer key may get rejected while the DNS record is not yet known everywhere in the world.

Using maps is simple. First, we need to change the selector_map setting of the dkim_signing module. To do that create a new file in /etc/rspamd/local.d/dkim_signing.conf and make it contain just these two lines:

path = "/var/lib/rspamd/dkim/$domain.$selector.key";
selector_map = "/etc/rspamd/dkim_selectors.map";

The configuration is pretty self-explaining. Rspamd will look for the domain-to-key mapping in the dkim_selectors.map file. Create that file and make it contain this line:

example.org 20250626

That’s all really. Rspamd now knows that whenever it sees an outgoing email from anyone@example.org it will get the DKIM private key from /var/lib/rspamd/dkim/example.org.20250626.key and use it to sign the email.

Reload the configuration:

sudo systemctl restart rspamd

Adding the domain key to Rspamd

Take the private key that was created earlier (the multi-line string including “…BEGIN PRIVATE KEY…” and “…END PRIVATE KEY…“) and put it into a file at the location where Rspamd will look for it:

/var/lib/rspamd/dkim/example.org.20250626.key

The name of the file has to be DOMAIN + dot + SELECTOR + “.key” like above. If you name the file incorrectly you will get an error in your rspamd.log file like "lua_dkim_sign_handler: cannot load dkim key /var/lib/rspamd/dkim/example.org.dkim.key".

Make sure that only _rspamd can read it:

sudo chown _rspamd /var/lib/rspamd/dkim/umd.me.uk.20250626.key
sudo chmod u=r,go= /var/lib/rspamd/dkim/umd.me.uk.20250626.key

Rspamd will automatically pick up the files and does not need to be restarted.

Make Postfix use Rspamd

Let’s tell Postfix to send all incoming email through Rspamd. Run these commands on the shell:

sudo postconf smtpd_milters=inet:127.0.0.1:11332
sudo postconf non_smtpd_milters=inet:127.0.0.1:11332
sudo postconf milter_mail_macros="i {mail_addr} {client_addr} {client_name} {auth_authen}"

Postfix will connect to Rspamd that is listening to TCP port 11332 on localhost (127.0.0.1) and send the email through that connection. The smtpd_milters setting defines that connection for emails that came into the system from the internet via the SMTP protocol. The non_smtpd_milters setting is optional – it makes Postfix have all emails checked that originate from the system itself. Finally, the milter_mail_macros setting defines several variables that Rspamd expects for better spam detection. Rspamd then runs its checks and tells Postfix whether the email should pass or get rejected.

Send a test email

If you have another email account at another location then you could just send a test email there via your mail server. If you take a look at the received email it should have a DKIM header now like:
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=umd.me.uk; s=20260121;
    t=1769108454;
    h=from:from:reply-to:subject:subject:date:date:message-id:message-id:
     to:to:cc:mime-version:mime-version:content-type:content-type:
     content-transfer-encoding:content-transfer-encoding:
     in-reply-to:in-reply-to:references:references;
    bh=Cx91GHgjW61hMAScYshCO3hCt4i+N5TQeiR3v/060u8=;
    b=mbYVHhxxYDWSHI7bKo5atScvpV7aei7eJ...
        
To verify the signature install the opendkim-tools package, copy the entire test email (including headers and body), run opendkim-testmsg in your shell and paste the email (finish with CTRL-D).

If you get no output then the signature verified correctly. But if you get something like “opendkim-testmsg: dkim_eom(): Unable to verify” then double-check your DNS record.

You can also use websites like dkimvalidator.com, isnotspam.com or mail-tester.com service to verify that your signatures are working well.

SPF and DMARC

Adding DKIM signatures is a good first step. But you can take it further by telling receiving mail servers that they should not accept any email from your domain without a valid signature or from servers that you do no operate. There are two concepts that aim to help. The older SPF and the newer DMARC. Either of them means creating a machine-readable string in a predefined format and adding a TXT record to your DNS zone. Receiving mail servers can check those records and take your advice (as the domain owner) what to do if the criteria of the email are not met. It could accept the email anyway or flag it as spam or reject it altogether.

Let’s take a look at a typical SPF record:

"v=spf1 ip4:157.97.194.11 mx ~all"

What it means:
  1. this is an SPF record of version 1 of the standard (there is currently no other version)
  2. please accept emails from the IP address 157.97.194.11
  3. alternatively accept emails from any server that is mentioned in our domain’s MX record (the server(s) that receive email for your domain)
  4. any other email should be considered suspicious – it might be spam or worse

There are websites that help you create your SPF string to add to your DNS domain. Keep in mind though:

I mentioned that DMARC is the newer standard. So why use SPF anyway? Because some email providers value your effort if you use SPF, too. Technically it’s sufficient to specify a DMARC entry. In my opinion restricting the IP addresses allowed to send is a little dangerous and a little inflexible. It is far more interesting to require that emails from your domain have a valid DKIM signature. A DMARC record may look like:

"v=DMARC1; p=reject; sp=reject; adkim=s; aspf=s;"

Migrate the mailserver and Roundcube database

You need to copy the database that contains the control data about your email domains and accounts. Log into the old (Bullseye) server as root or sudo account and back up the mailserver and Roundcube database. That is as easy as running…

mysqldump mailserver > mailserver.sql
mysqldump roundcube > roundcube.sql

Copy the files to the new server (using scp) and import them there:

mysql mailserver < mailserver.sql
mysql roundcube < roundcube.sql

Obviously, any database changes on the old server from now on will have to be done on the new server as well until the migration is done.

A solution to avoid the name conflict when migrating is to do it in two steps. First, you set up a new server with a new name, e.g., mail.example.org. Copy everything there and then delete the old sever. Now, you set up another server with the original name, e.g., webmail.example.org. This solution is simple although requires you to migrate twice. It's like you move all your stuff from one room to another, completely upgrade the original room, and then move the stuff back again. An advantage of this method that you will get 100/100 score regarding MX in www.dnsinspect.com. If you use CNAME in MX, you will get a warning:

WARNING: Found CNAMEs in MX records, invalid MX records:

RFC 2181, section 10.3 says that host name must map directly to one or more address record (A or AAAA) and must not point to any CNAME records. RFC 1034, section 3.6.2 says if a name appears in the right-hand side of RR (Resource Record) it should not appear in the left-hand name of CNAME RR, thus CNAME records should not be used with NS and MX records. Despite these restrictions, there are many working configurations using CNAME with NS and MX records.

About Redis

Many features in Rspamd use Redis to persist their data. Let me give you a quick explanation what Redis is.

Redis is a kind of database system. It is way more limited than a traditional SQL database because it just stores keys and values. There aren’t several fields/columns like in SQL. But it is lightning fast the way it works. On my aged server it handles around 50,000 requests per second. It gets it speed from its simplicity and from keeping the data in RAM. So it doesn’t access the disk to fetch information. (But it copies its data to disk frequently to prevent data loss.) People use Redis as a cache or for very fast lookups of simple data structures. And so does Rspamd.

You have optionally installed the “redis-server” package earlier. And that’s all you needed to do. It started automatically and listens ton incoming connections on TCP port 6379 on localhost. In Rspamd the Redis backend is enabled by default. You just have to tell it the IP address of your Redis server. Add a file /etc/rspamd/override.d/redis.conf and insert:

servers = "127.0.0.1";

Restart Rspamd and you are done.

sudo systemctl restart rspamd

The web interface

Rspamd comes with a neat bonus feature: a web interface. It allows you to check emails for spam, get statistics and fine-tune scores. It is already installed and enabled by default and expects HTTP requests on port 11334 on the localhost interface. I suggest you add a simple proxy configuration to your already working HTTPS-enabled web mail configuration to get access.

First you need to enable Apache’s modules for HTTP proxying and rewriting:

a2enmod proxy_http
a2enmod rewrite

You can either create a new virtual host configuration or just edit the /etc/apache2/sites-available/webmail.example.org-https.conf file. Anywhere within the VirtualHost tags add:

<Location /rspamd>
Require all granted
</Location>

RewriteEngine On
RewriteRule ^/rspamd$ /rspamd/ [R,L]
RewriteRule ^/rspamd/(.*) http://localhost:11334/$1 [P,L]

This piece of configuration will forward any requests to https://webmail.example.org/rspamd to localhost:11334 and thus give you access to the Rspamd web interface.

The interface is password protected. Let’s generate a new access password:

pwgen 15 1

This gives you a password like “eiLi1lueTh9mia4”. You could put that password in an Rspamd configuration file. But cleartext passwords in configuration files are not quite elegant. Let’s create a hash of the password:

rspamadm pw
Enter passphrase: …
$2$icoahes75e7g9wxapnrbmqnpuzjoq7z…

Feed it the password that pwgen created for you and you will get a long hashed password. This procedure by the way is also documented on the Rspamd pages.

Create a new configuration file /etc/rspamd/local.d/worker-controller.inc and put your hashed password in there:

password = "$2$icoahes75e7g9wxapnrbmqnpuzjoq7z…"

That’s it for the configuration. Finally restart both Rspamd and Apache to load your changed configuration:

sudo systemctl restart rspamd
sudo systemctl restart apache2

If everything went as expected you should now be able to access the Rspamd web interface at https://webmail.example.org/rspamd

Full examples

I hope these tutorials helped you set up tile, routing, geocoding, and email server. You can view the full examples as follows: