Deploying Exchange 2016 Behind NGINX Free

Once I had Exchange installed with mail flowing in both directions it was time to give my client devices access from outside my network. In my case this is limited to iOS devices using Activesync and Outlook Web Access (OWA) in the browser.

As a customer of Verizon Fios on a residential plan I am unfortunately limited to one dynamic IP address. Equally as unfortunate it appears to be difficult or impossible to get Activesync or OWA working on non standard ports so there is no choice but to allocate ports 80 and 443 to Exchange. As I already have a slew of services running on those ports I have an NGINX reverse proxy running to server all of my internal sites. Unforuntately I had no choice but to put Exchange behind the proxy as well. This is not as simple as it sounds as there are a considerable number of steps and configurations necessary to get this working.

WARNING: Before starting this process forward your ports directly to Exchange and make sure everything works perfectly. I can't tell you how many hours I spent troubleshooting the NGINX only to finally find out I had a simple Exchange problem all along. Learn from my mistakes and make sure the simple things are in order before facing the more difficult ones. A great tool to use is MS Remote Connectivity Analyzer which gives much more diagnostic information than Outlook or your iPhone. Don't forget to send those ports back to NGINX when you are done.

The first step is to get a working subdomain. For the purposes of this tutorial we will use mail.contoso.com. If you have a static ip create an A record for host "mail" pointing to your static IP. If you have a dynamic IP create a CNAME record pointing "mail" to your dynamic DNS hosname. Once that zone file is saved I would suggest pinging "mail.contoso.com" to ensure that it is resolving to your IP address.

Next we will configure Exchange to recognize your new sub domain as the external address. For OWA, ECP and Activesync. Point your browser to your Exchange ECP and choose "servers" on the left hand side and then "virtual directories" from, the top of the right hand pane.

Then for each OWA, ECP and Microsoft-Server-ActiveSync click the wrench icon and enter the external access URL you created above.

Once that is complete, this is where I would test Exchange without NGINX to make sure everything is working. In my case I spent hours troubleshooting NGINX when the problem was that I had an Active Directory user issue.

Once everything is working its time to configure NGINX. Since all of my other services share an SSL certificate and NGINX configuration file and I didn't want to interfere with that configuration I decided to create a separate certificate and configuration file for Exchange.

Go ahead and create a letsencrypt certificate for your new subdomain and note the location. On my Debian install letsencrypt puts all certificates in /etc/letsencrypt/sub.domain.tld/.

Once you have the certificate and NGINX is working its time to create the configuration file. This assumes you have NGINX working. If not, take care of that now and come back.

The next step is to install NGINX extras. Hat tip to tail call blog for this step. You need the HttpHeadersMore module in order to forward the correct headers. This can be gotten with apt-get install nginx-extras.

Now its time to create a configuration file sudo nano /etc/NGINX/conf/exchange.confd

After much trying this is the configuration which finally worked for me. Credit to tail call blog and planetit for the bulk of this,

server {
    listen 80;
    #listen [::]:80;
    server_name mail.contoso.com autodiscover.contoso.com;
    return 301 https://$host$request_uri;
}

server {
    tcp_nodelay on;
    #keepalive_timeout 3h;
    #proxy_read_timeout 3h;
    listen 443;
    #listen [::]:443 ipv6only=on;
    ssl                     on;
    ssl_certificate /etc/letsencrypt/live/mail.contoso.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mail.contoso.com/privkey.pem;

    ssl_session_timeout     5m;
    server_name mail.contoso.com;

    location / {
            return 301 https://mail.contoso.com/owa;
    }

    proxy_http_version      1.1;
    proxy_read_timeout      360;
    proxy_pass_header       Date;
    proxy_pass_header       Server;
    proxy_pass_header      Authorization;
    proxy_set_header        Host $host;
    proxy_set_header        X-Real-IP $remote_addr;
    proxy_set_header        X-Forwarded-For  $proxy_add_x_forwarded_for;
    proxy_pass_request_headers on;
    more_set_input_headers 'Authorization: $http_authorization';
    proxy_set_header Accept-Encoding "";
    more_set_headers -s 401 'WWW-Authenticate: Basic realm="LOCALURL"';
    # proxy_request_buffering off;
    proxy_buffering off;
    proxy_set_header Connection "Keep-Alive";

    location ~* ^/owa { proxy_pass https://LOCALURL; }
    location ~* ^/Microsoft-Server-ActiveSync { proxy_pass https://LOCALURL; }
    location ~* ^/ecp { proxy_pass https://LOCALURL; }
    location ~* ^/rpc { proxy_pass https://LOCALURL; }
    #location ~* ^/mailarchiver { proxy_pass https://mailarchiver.local; }

    error_log /var/log/nginx/owa-ssl-error.log;
    access_log /var/log/nginx/owa-ssl-access.log;
}

Replace wherever it says LOCALURL with the local IP address or DNS name that NGINX can use to find your Exchange server.

Save and restart NGINX

Now test again with MS Remote Connectivity Analyzer. It should work and you should be good to go.

Adam Smith

Read more posts by this author.