#g33kr_

Hosting email at home

If you've ever wanted to move your email out of the cloud and host it at home on your own email server, you've probably found that your residential broadband ISP has done everything they can to prevent you from actually doing so. From dynamic IP address allocation to blocked TCP ports to the fact that residential broadband netblocks are generally included in every online spam blacklist, the cards are stacked against you when it comes to self-hosting email at home. But, there is a way to get around all of those roadblocks and host your email securely on a server in your home, for as little as $5 per month.

This post doesn't go into depth about actually setting up the base email server config. For that, you can check out my previous posts about setting up an IMAP and SMTP server and adding spam filtering and anti-virus. This post will add-on to the configurations provided in those previous posts to facilitate hosting such an email server at home on residential broadband.

The pitfalls of hosting at home

Anyone trying to host an email server at home will quickly discover some non-trivial challenges:

  • Blocked TCP ports. Email exchange on the Internet is performed using the SMTP protocol, and the standard port for SMTP is TCP port 25. Most residential ISPs block TCP/25 outbound at a minimum, and many also block TCP/25 inbound. Obviously, this presents a problem if your mail server is unable to reach other mail servers, or they are unable to reach you.

  • Dynamic public IP allocation. Most residential ISPs assign their customers' public IP addresses dynamically, using DHCP. With some broadband providers that IP allocation may not change very often, with others it may change every few days. Or it may change if you have an extended power outage and your DHCP lease expires. Or it may change if you replace your firewall or even just swap a NIC card, due to a change in your public-facing MAC address. There are some workarounds to this problem involving dynamic dns, but such solutions can be kludgy and unreliable for something like a mail server. The best option is to have an IP for your mail server that doesn't change.

  • Lack of reverse lookup. DNS has two components, forward lookup (name to IP) and reverse lookup (IP to name). These days you will very often find that email servers are configured to check DNS to ensure that the reverse lookup of a connected IP matches the name that the connecting server claims to be. If the reverse lookup doesn't match, the connection is often dropped or at the very least any email from the sending host is considered highly suspect. So while it's easy to point a forward DNS record for mail.yourdomain.com to the IP address you get from your cable modem, the reverse (PTR) record is usually beyond your control. A reverse lookup will return something like cpe-22-33-44-101.region.your.isp.com.. Which most likely means your email to other domains will be rejected.

  • Unreliable connectivity. Do you have a rock solid home Internet connection, that is always fast and never goes down? Yeah, me neither. But when you host your email server at home and your Internet goes down, what happens to your incoming email? Well, it depends. For brief outages of only a few hours, the sending email server will likely try to resend your email several times before finally giving up. But what if for example, you lose power for several days due to inclement weather? Even if your email is eventually delivered, the sender may receive one or more disconcerting delivery status notifications (DSN) notifying them of delay in delivery. In the worst case, that email is gone. Which might be inconvenient if it was someone emailing you about a job offer, or to tell you that your Aunt Trudy is in the hospital.

  • ISP Terms of Service. Many (if not most) ISPs have a written policy prohibiting the hosting of public-facing servers on their residential connections. The degree to which these terms are enforced varies by ISP and locale. You may host a server at home for years and never hear a peep out of your ISP. Or you may get a letter demanding that you either take the server offline, or you'll be automatically upgraded to the ISP's premium-cost "business class" service. Or they may simply discontinue your service altogether, citing a violation of the policy you agreed to when you signed up.

  • Blacklists. And then we come to the biggest roadblock to hosting at home: spam blacklists. Blacklists (also called DNSBLs or RBLs) are public services that host a list of reported spam sources. As Internet spam has become more and more of a problem over the last 20 years or so, the use of spam blacklists by email administrators has become prolific. And inclusion of your mail server's IP address in one of these lists (rightly or wrongly) will pretty much guarantee that your email will not be delivered. Most public spam blacklists include the known IP subnet ranges allocated by ISPs to their residential customers, and IPs in those networks are regarded as invalid email sources by default. Some email gateways won't even accept your SMTP session if you're on a residential broadband network. The connection is simply dropped.

Wow, that's a lot of roadblocks. So how do we get around all of this?

Email by proxy

What we need is a server that we control, that is hosted in the cloud on a network that other email servers will accept mail from, and through which we can send our mail. We don't want to store our email on this cloud server, since the whole point of bringing our email in-house was to get it out of the cloud. Instead, we just want to forward email to and from our home server through this proxy in a secure and reliable manner.

Required components

To make this work, you'll need a few things:

  1. A home email server. This box will be the physical or virtual server you host at home on your own hardware. For the purpose of this config, I'll assume your mail server is running CentOS 7 and that you're using Postfix as your MTA.

  2. A cloud email server. This is going to be a lightweight virtual private server (VPS) that you host at an online provider. I would recommend DigitalOcean as your provider for a number of reasons. First, they are inexpensive. The base "droplet" (VPS) is just US$5 per month and includes 512MB of RAM, 20GB of SSD storage, and 1TB of monthly transfer. Which should be more than sufficient to host your personal-use email server. Second, they provide each droplet with a static IPv4 (and at some locations, IPv6) address at no additional charge, and they allow you to setup reverse DNS for your VPS' IP address. Their administration console is easy to use, and I've found their service to be very reliable. Plus, they accept payment by PayPal, so you don't need to give them your credit card number. That's a big win as far as I'm concerned. Again, for this config I'll assume your VPS is also running CentOS 7 and Postfix.

  3. An SSL certificate. While not strictly required, I'd strongly recommend getting an SSL certificate for at least your cloud mail server, so that you can enable Transport Layer Security (TLS) encryption on your connections to other email servers that also support TLS. Without TLS, your email is passed to remote mail servers in the clear. Which is kind of like leaving your bedroom blinds open while you get undressed, in a neighborhood full of peeping Toms.

The topology for this setup will look like:

DNS
yourdomain.com      IN MX 10 smtp.yourdomain.com.
smtp.yourdomain.com IN A     11.22.33.44
11.22.33.44            PTR   smtp.yourdomain.com.

smtp.yourdomain.com
    11.22.33.44
        +------------+  Inbound and outbound SMTP
        |     VPS    |      <--------------->
        | mail proxy |~~~~
        +------------+    )
tun0           | (  cloud  )
192.168.254.1 +=+ (  host )
            ^ | |  ~~~~~~~
   ~~~~~~~~~|~| |~~~~~~~~~~~
  (         | | |  public   )
 (          | | | Internet   )
  (         | |V|           )
   ~~~~~~~~~|~|P|~~~~~~~~~~~
  in and out| |N|  ~~~~~~~~~~~
  SMTP relay| | | (           )
            | | |( residential )
            | | | (    ISP    )
            | | |  ~~~~~~~~~~~ 22.33.44.xx (DHCP)
            | | | +------------+
 tun0       v +=+ |  firewall  |
 192.168.254.6 |  +------------+
     +-------------+    ~|~~~~ 192.168.1.1
     |     home    |---(-+    )
     | mail server |  (  LAN   )
     +-------------+   (      )
      =====|||=====     ~~~~~~
      | mailboxes |
      =============
home.yourdomain.com
    192.168.1.10

Fig. 1: Incoming email is routed to the cloud server, and then relayed to the home server over VPN. Outbound email is relayed from the home server through the cloud server.

Let's get started.

The home email server

I'm going to assume you already have your home email server installed and configured. If not, run through my how-to for setting up a mail server on CentOS 7 with Postfix and Dovecot. There are only a couple of changes we'll have to make to the Postfix configuration on the home side, and the first is to set a smarthost address. Edit your /etc/postfix/main.cf file, and add the following line:

relayhost = 192.168.254.1

This config tells Postfix to forward all email not destined for local delivery to your relay host in the cloud, via the VPN tunnel we're going to build. We also need to add the VPN network to the mynetworks line, also in /etc/postfix/main.cf:

mynetworks = 127.0.0.0/8, 192.168.1.0/24, 192.168.254.0/24

Now just refresh Postfix's configuration:

[root@home /]# postfix reload
postfix/postfix-script: refreshing the Postfix mail system

The cloud email server

Depending on the packages pre-installed in your hosting provider's default VPS image, Postfix may or may not be installed. If it isn't, install the package:

[root@smtp /]# yum install postfix

Your configuration on the cloud side will be similar to your config on the home side, except that you won't need SMTPS or SASL authentication. Here's a sample config. Note two important lines. First, we again add our VPN network to the mynetworks line. Second, we add an additional line with the transport_maps directive.

myhostname = smtp.yourdomain.com
mydomain = yourdomain.com
myorigin = $mydomain
mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
mynetworks = 127.0.0.0/8, 192.168.254.0/24
relay_domains = $mydestination
inet_interfaces = all
inet_protocols = all

# we'll use transport_maps to route inbound email over the VPN tunnel
transport_maps = hash:/etc/postfix/transport

alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
append_dot_mydomain = no
biff = no
soft_bounce = no
recipient_delimiter = +
queue_directory = /var/spool/postfix
command_directory = /usr/sbin
daemon_directory = /usr/libexec/postfix
data_directory = /var/lib/postfix
mail_owner = postfix
default_privs = nobody

unknown_local_recipient_reject_code = 550
unknown_address_reject_code = 550
unknown_hostname_reject_code = 550
unknown_client_reject_code = 550

in_flow_delay = 1s
default_destination_concurrency_limit = 5
relay_destination_concurrency_limit = 1
disable_vrfy_command = yes
message_size_limit = 26214400 #25MB

smtpd_banner = $myhostname ESMTP
smtpd_helo_required = yes
smtpd_helo_restrictions =
        permit_mynetworks,
        reject_non_fqdn_helo_hostname,
        reject_invalid_helo_hostname,
        permit
smtpd_recipient_restrictions =
        reject_unauth_pipelining,
        reject_non_fqdn_recipient,
        reject_unknown_recipient_domain,
        permit_mynetworks,
        reject_unauth_destination,
        permit
smtpd_sender_restrictions =
        permit_mynetworks,
        reject_non_fqdn_sender,
        reject_unknown_sender_domain,
        permit

smtpd_tls_cert_file = /etc/pki/tls/certs/yourdomain.com.crt
smtpd_tls_key_file = /etc/pki/tls/private/yourdomain.com.key
smtpd_tls_ciphers = high
smtpd_tls_loglevel = 1
smtp_tls_security_level = may
smtpd_tls_security_level = may
smtpd_tls_session_cache_timeout = 3600s
smtpd_tls_auth_only = yes
smtpd_tls_received_header = yes
smtp_tls_note_starttls_offer = yes
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
tls_random_source = dev:/dev/urandom

sendmail_path = /usr/sbin/sendmail.postfix
newaliases_path = /usr/bin/newaliases.postfix
mailq_path = /usr/bin/mailq.postfix
setgid_group = postdrop

Now edit the /etc/postfix/transport file and add the following line:

yourdomain.com   smtp:[192.168.254.6]

You'll need to compile this file any time you make a change to it, using the postmap command:

[root@smtp postfix]# postmap /etc/postfix/transport

If you're running firewalld be sure to open TCP port 25 to allow SMTP traffic through:

[root@smtp /]# firewall-cmd --zone=public --permanent --add-service=smtp
[root@smtp /]# firewall-cmd --reload

Then enable and start Postfix:

[root@smtp /]# systemctl enable postfix
[root@smtp /]# systemctl start postfix

Ensure that Postfix started and is running on port 25:

[root@smtp /]# netstat -an | more
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 0.0.0.0:25              0.0.0.0:*               LISTEN

Setting up the VPN tunnel

You now have a local SMTP server and a cloud SMTP server, and it's time to connect the dots. For this, you'll create a simple server-to-server VPN tunnel using OpenVPN. The cloud email server will be the OpenVPN server and your home server will be the OpenVPN client. Start by installing OpenVPN on both the home server and the cloud server. OpenVPN is not included in the CentOS repos, so first you'll have to enable the EPEL repository if you haven't done so already:

[root@smtp /]# yum install epel-release
[root@smtp /]# yum update

Then you can install the openvpn and easy-rsa packages:

[root@smtp /]# yum install openvpn easy-rsa

Now you'll need to create some certificates to encrypt and authenticate your VPN connection. This can be a somewhat involved process, and I'm going to just run through the highlights. If you'd like more info on OpenVPN and creating certificates, check out this excellent tutorial by Jacob Tomlinson, hosted by DigitalOcean. You only need to go through this cert generation process on the server side. Once you're done, you'll just copy a few of the certificate files to the home server for the client side of the connection.

First, you're going to create a directory to build the keys and copy the easy-rsa starter files into it. You're also going to create a symlink for the openssl config file, in case your openssl version isn't detected properly:

[root@smtp /]# mkdir -p /etc/openvpn/easy-rsa/keys
[root@smtp /]# cp -rf /usr/share/easy-rsa/2.0/* /etc/openvpn/easy-rsa
[root@smtp /]# ln -s /etc/openvpn/easy-rsa/openssl-1.0.0.cnf /etc/openvpn/easy-rsa/openssl.cnf

Now enter the following commands:

[root@smtp /]# cd /etc/openvpn/easy-rsa
[root@smtp easy-rsa]# source ./vars
[root@smtp easy-rsa]# ./clean-all

And now you'll build your certificate authority (CA) cert. You'll be prompted to provide some certificate details. It really doesn't matter what you enter for country, province, city, organization, organizational unit, or email. The only two fields that matter are the certificate name (which you should call 'server') and the canonical name (CN), which should be the fully-qualified name of your server.

[root@smtp easy-rsa]# ./build-ca

You'll repeat the process to build the server certificate:

[root@smtp easy-rsa]# ./build-key-server server

Next you'll create your Diffie-Hellman (DH) key exchange file. This can take a while:

[root@smtp easy-rsa]# ./build-dh

Now create a client certificate for your home server:

[root@smtp easy-rsa]# ./build-key client

And then finally, we're going to create a TLS-auth key for an additional layer of security, and then copy the necessary keys to the /etc/openvpn directory:

[root@smtp easy-rsa]# cd keys
[root@smtp keys]# openvpn --genkey --secret ta.key
[root@smtp keys]# cp dh2048.pem ca.crt server.crt server.key ta.key /etc/openvpn

You'll need to copy the ca.crt, client.crt, client.key, and ta.key files to the /etc/openvpn directory on your home server, as well.

A WORD OF WARNING: Don't leave your ca.key file on your server. It's not needed there, and if someone were to get it (and the ta.key file), they could sign their own client cert and connect to your server. My advice is to backup all of your OpenVPN keys, and then rm -rf /etc/openvpn/easy-rsa rather than leaving it laying around. At the very least, backup the ca.key file and delete it from the server.

Now with keys and certs in place, you just need to create some configuration files. On the server side, create /etc/openvpn/server.conf:

port 31999 #just a high, random port
proto udp
dev tun
ca ca.crt
cert server.crt
key server.key
dh dh2048.pem
server 192.168.254.0 255.255.255.0
ifconfig-pool-persist ipp.txt
keepalive 10 120
tls-auth ta.key 0
comp-lzo
max-clients 1
user nobody
group nobody
persist-key
persist-tun
status /var/log/openvpn-status.log
log /var/log/openvpn.log
verb 4
mute 20

If you're running firewalld open up UDP port 31999:

[root@smtp /]# firewall-cmd --zone=public --permanent --add-port=31999/udp
[root@smtp /]# firewall-cmd --reload

Now enable and start openvpn@server:

[root@smtp /]# systemctl enable openvpn@server
[root@smtp /]# systemctl start openvpn@server

Ensure that the OpenVPN service started and is running on port 31999:

[root@smtp /]# netstat -an | more
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
udp        0      0 0.0.0.0:31999           0.0.0.0:*               LISTEN

Now configure the client side on the home server. Edit the /etc/openvpn/client.conf file: client dev tun proto udp remote 11.22.33.44 31999 # use the IP address of your VPS resolv-retry infinite nobind user nobody group nobody persist-key persist-tun ca ca.crt cert client.crt key client.key tls-auth ta.key 1 comp-lzo verb 3 mute 20

Enable and start the openvpn@client service:

[root@home /]# systemctl enable openvpn@client
[root@home /]# systemctl start openvpn@client

Within a few moments, the tunnel should establish. Verify by using the ifconfigcommand. You should see atun0` interface:

tun0: flags=4305<up,POINTOPOINT,RUNNING,NOARP,MULTICAST>  mtu 1500
        inet 192.168.254.6  netmask 255.255.255.255  destination 192.168.254.5
        unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  txqueuelen 100  (UNSPEC)
        RX packets 12165  bytes 15687148 (14.9 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 7096  bytes 773505 (755.3 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0</pre>`

Verify connectivity by pinging 192.168.254.1:

[root@home /]# ping -c 4 192.168.254.1
PING 192.168.254.1 (192.168.254.1) 56(84) bytes of data.
64 bytes from 192.168.254.1: icmp_seq=1 ttl=64 time=29.3 ms
64 bytes from 192.168.254.1: icmp_seq=2 ttl=64 time=33.5 ms
64 bytes from 192.168.254.1: icmp_seq=3 ttl=64 time=35.6 ms
64 bytes from 192.168.254.1: icmp_seq=4 ttl=64 time=33.1 ms

--- 192.168.254.1 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3005ms
rtt min/avg/max/mdev = 29.368/32.928/35.669/2.273 ms

DNS configuration

At this point, you're ready to point the MX record for your domain to the IP of your cloud VPS. Create an A record for your VPS' fully qualified name, and then set your MX record to point to the A record. Also, be sure to setup a reverse DNS PTR record for your IP, one that resolves to the fully qualified name of your VPS. You may need the assistance of your cloud hosting provider to accomplish this, if they don't have such a function in their user control panel. DNS updates can take some time to go into effect, depending on the TTL of the record being changed. Keep that in mind while testing.

Once your DNS records have updated, try sending yourself a test message from an external account such as Gmail. Watch your mail logs on both the VPS and your home server while you send the mail, using the command tail -f /var/log/maillog. You should see the email arrive at the VPS, be relayed to the home server, and then be deleted from the VPS. Also try sending an outbound email through your home server, and you should see basically the same process in reverse. If your emails aren't getting through, examine /var/log/maillog for the reasons.

ONE CAVEAT REGARDING TLS AND CERTIFICATE RE-USE: If you have already bought a third-party SSL certificate to use for your home email server, you may consider just using the same cert on your cloud email server rather than buying a second cert. Unfortunately, that won't work. If, when the mail servers attempt to negotiate TLS with each other, they see their own cert on the remote end, each server will believe that it has connected back to itself. In order to prevent a mail delivery loop, the sending server will refuse to send any email and will terminate the connection. So if TLS is used on both the cloud server and the home server, each side must have it's own unique SSL certificate.

Wrapping up

If everything has gone according to plan, you should now be able to send and receive email to and from other Internet domains from your home email server by routing the mail through your VPS server in the cloud. Let's look at the self-hosting challenges we've overcome:

  • Local storage of email. Our email is now hosted on our own server at home, and is no longer stored in the cloud. The only email that will remain on your VPS for any length of time will be messages that are deferred for some reason, and even those will expire and be deleted, generally within five days.

  • Blocked TCP ports. We've worked around our ISP's port blocking by passing email traffic in and out over an unblocked VPN tunnel.

  • Dynamic public IP allocation. Our cloud server has a static IP address, and our home IP address no longer matters.

  • Lack of reverse lookup. We have DNS reverse lookup for our cloud server's IP address, which will make remote email servers happy when our server connects.

  • Unreliable connectivity. Our connectivity at home is no longer a critical component of mail flow. If your home Internet goes down and the VPN tunnel drops, mail will be cached on either side and delivery will be attempted later when the tunnel is restored. By default, Postfix will hold deferred email for five days. If you want to set this hold time to a longer or shorter duration you can use the maximal_queue_lifetime directive in your /etc/postfix/main.cf file.

  • ISP Terms of Service. We aren't hosting a public-facing server on our residential connection, so we aren't in violation of our ISP's Terms of Service.

  • Blacklists. And finally, because our email is being sent from our cloud server on an IP address that isn't part of a residential broadband network, we won't necessarily be blocked. I say necessarily because your cloud server's IP may or may not be on one or more blacklists, especially if it was previously issued to someone who stood up a VPS for the purpose of spamming. MXToolbox.com has an online tool you can use to check your IP against public blacklists. I recommend you check your IP when you first stand up your VPS, before you do any serious configuration. If your newly-issued IP is on a blacklist you can either try to get off the list, or you can work with your provider to get issued a new IP. Or, failing that just delete the VPS and create a new one, and you'll likely get issued a different IP. Best to do that before you put in a lot of work configuring your mail server, though.

Anyway, that's about it. This has been one monster post, I hope you have found it useful. If you have questions, corrections, or feedback, drop me an email. Thanks for reading!