Trent Steenholdt's Blog

A simple blog sharing my findings, interests, and work in the Information Technology industry, as well as anything else that interests me.

How to Get Exchange 2019 Working Behind NGINX Proxy Manager

trentsteenholdt
September 10, 2025

14 minutes to read

Running Microsoft Exchange 2019 on-premises in 2025 is still a reality for many organisations. Publishing it securely to the internet is usually straightforward when using one of the recommended reverse proxy solutions. The challenge comes when you step outside those conventional options, because Exchange is not a typical web application. It relies on multiple services such as Autodiscover, EWS, MAPI over HTTP, RPC, ActiveSync, and OWA/ECP, each with its own authentication requirements. When Exchange is placed behind a reverse proxy such as NGINX Proxy Manager (NPM), this complexity quickly becomes apparent.

For me, Exchange has mainly been part of my home environment. Until very recently I was still publishing it through a Microsoft TMG (Threat Management Gateway) server running on Windows Server 2008 R2. That setup had been in place for years and it worked reliably for my needs at home. However, by now, 2025, it was clear that it was time to finally retire TMG and move to something current.

That is where NGINX Proxy Manager came in. It looked like the perfect lightweight replacement with a friendly UI and built-in certificate management. What I discovered is that while NPM works great for most web apps, Exchange requires a more careful approach.

Why NGINX Proxy Manager?

NGINX Proxy Manager is popular in home and lab environments because it:

  • Has a clean web interface for managing proxies and certificates.
  • Handles Let’s Encrypt certificate requests and renewals automatically. Wildcard and SAN Certificates have gotten really expensive lately!
  • Allows per-host “Advanced Config” blocks and custom locations.
  • Is lightweight and easy to deploy in Docker.

This makes it ideal for exposing services like Home Assistant, Jellyfin, and some the IIS websites I still run.

Exchange 2019, however, is not just another web app. It uses:

  • NTLM authentication for legacy clients like Outlook and ActiveSync.
  • Modern OAuth flows for OWA and ECP when Hybrid Modern Authentication is enabled and set up correctly.
  • Multiple virtual directories, each with its own requirements and quirks.

The result? If you just point NPM at an Exchange server without adjustments, things just don’t work.

Why People Fail with Nginx Proxy Manager and Exchange

There are many GitHub issues where people have tried and failed:

  • #3797 – endless login loops for OWA.
  • #1138 – “bad request” errors for MAPI.
  • #2037 – ActiveSync failing.
  • #1117 – NTLM authentication broken.

Some of these even suggests roling back NTLM Authentication in favour for Basic Authentication. Something I could never recommend. The root cause is always the same: the stock Nginx Proxy Manager Docker image does not include the NGINX NTLM module. Without this module:

  • NTLM handshakes cannot complete.
  • Outlook clients fail to connect.
  • ActiveSync breaks on iOS and Android.

Some assume the only solution is to pay for NGINX Plus, since that officially supports NTLM. But for a home environment, that is overkill and expensive.

The Fix: Open Source NTLM Module

The good news is that the open-source NTLM module has existed for years and works perfectly fine once compiled into NGINX. For those interested, that’s available at https://github.com/gabihodoroaga/nginx-ntlm-module.

That is why I chose the greetoz/nginx-proxy-manager fork. This project provides a pre-built Docker image for NPM with the NTLM module compiled in. With it you can:

  • Continue using the NPM web UI for proxy and SSL management.
  • Support NTLM for Autodiscover, EWS, RPC, MAPI, ActiveSync, and OAB.
  • Apply custom Advanced Config blocks without building your own container.

This single change saves hours of work and makes NPM viable for Exchange. It also saves you messing around too much with the docker-compose.yml.

NTLM vs Hybrid Modern Authentication for OWA and ECP

In my home setup, I have not moved on-premises Exchange environment to Modern Authentication, so NTLM is still required for Outlook and ActiveSync. The drawback is that without Forms Based Authentication support in NGINX Proxy Manager, both OWA and ECP default to NTLM, which results in an unpleasant browser pop-up prompt.

To avoid the family complaints about that prompt, I decided to migrate OWA and ECP to Modern Authentication instead.

By enabling Hybrid Modern Authentication (HMA) you can access the familiar Sign in to Outlook page, enforce MFA, and apply conditional access policies.

The process is already well documented in Alitajran’s excellent guide, but at a high level you must:

  1. Align OWA and ECP URLs so internal and external addresses match your public DNS.
  2. Register those URLs as reply URLs in Microsoft Entra ID.
  3. Disable all other legacy authentication methods (Basic, Forms, Windows, Digest, ADFS).
  4. Enable OAuth authentication for OWA and ECP.

Importand Note: This assumes you are running a hybrid Exchange configuration with Microsoft 365, as HMA depends on Azure AD/Entra integration. If you are running a purely on-premises Exchange environment without hybrid, you cannot use HMA! In that case you will need to stick with NTLM authentication for OWA and ECP, and accept the browser pop-up prompt.

Without this, users will continue to see the NTLM dialog box and miss out on modern security features.

Configuring Exchange

On my Exchange server CORTANA-EXCH04, I aligned my OWA and ECP URLs:

Set-OwaVirtualDirectory -Identity "CORTANA-EXCH04\owa (Default Web Site)" `
  -InternalUrl "https://owa.cortanadesign.com.au/owa" `
  -ExternalUrl "https://owa.cortanadesign.com.au/owa"

Set-EcpVirtualDirectory -Identity "CORTANA-EXCH04\ecp (Default Web Site)" `
  -InternalUrl "https://owa.cortanadesign.com.au/ecp" `
  -ExternalUrl "https://owa.cortanadesign.com.au/ecp"

Then I disabled all legacy authentication methods and enabled OAuth:

Get-OwaVirtualDirectory -Server "CORTANA-EXCH04" | Set-OwaVirtualDirectory `
  -AdfsAuthentication $false -BasicAuthentication $false `
  -FormsAuthentication $false -DigestAuthentication $false `
  -WindowsAuthentication $false -OAuthAuthentication $true

Get-EcpVirtualDirectory -Server "CORTANA-EXCH04" | Set-EcpVirtualDirectory `
  -AdfsAuthentication $false -BasicAuthentication $false `
  -FormsAuthentication $false -DigestAuthentication $false `
  -WindowsAuthentication $false -OAuthAuthentication $true

Verification:

Get-OwaVirtualDirectory -Server "CORTANA-EXCH04" | fl server,*auth*
Get-EcpVirtualDirectory -Server "CORTANA-EXCH04" | fl server,*auth*

Output should show only OAuthAuthentication: True with everything else disabled.

Configuring NGINX Proxy Manager

In NGINX Proxy Manager, I created a Proxy Host for owa.cortanadesign.com.au:

  • Domain Names: owa.cortanadesign.com.au, autodiscover.cortanadesign.com.au
  • Scheme: HTTPS
  • Forward Hostname/IP: cortana-exch04.cortanadesign.com.au
  • Forward Port: 443
  • SSL: Let’s Encrypt certificate with “Force SSL” enabled
NGINX Proxy Manager 1 Screenshot
NGINX Proxy Manager 2 Screenshot
NGINX Proxy Manager 3 Screenshot

Defining the Exchange NTLM Upstream

The NGINX Proxy Manager UI does not allow you to define upstream blocks directly in the Advanced Configuration unless you disable the Proxy Host. Exchange works best with a dedicated upstream that has NTLM enabled, so we need to create this manually.

On the Docker host, create/ modify the file:

/opt/nginxproxymanager/data/nginx/custom/http_top.conf

Add the following content:

# Exchange Server NTLM upstream
upstream exch_ntlm {
    server cortana-exch04.cortanadesign.com.au:443 max_fails=3 fail_timeout=30s;
    ntlm;
    ntlm_timeout 3h;
    keepalive 64;
}

After saving the file, restart NGINX Proxy Manager and validate the configuration:

docker exec -it nginxproxymanager_app_1 nginx -t

(replace nginxproxymanager_app_1 with the actual name of your container if different)

This creates a persistent upstream for Exchange with NTLM enabled, a three-hour timeout for long Outlook sessions, and connection reuse via keepalive.

Why http_top.conf? Because NGINX Proxy Manager automatically includes this file at the top of the global http {} block each time it reloads its configuration. That means the exch_ntlm upstream is always available for your custom location blocks, without modifying NPM’s core templates.

General Advanced Configuration

NGINX Proxy Manager 4 Screenshot

With the http_top.conf updated, return to the Proxy Host and in the UI specify the Advanced → Custom Nginx Configuration with the following:

keepalive_timeout 3h;
tcp_nodelay on;
client_max_body_size 3g;

# buffering and timeouts
proxy_request_buffering off;
proxy_buffering off;
proxy_read_timeout 3h;
proxy_send_timeout 3h;
proxy_connect_timeout 20s;

# required for NTLM and stable upstream connections
proxy_http_version 1.1;
proxy_set_header Connection "";

# standard headers
proxy_set_header Accept-Encoding "identity";

# pass request headers upstream, and allow key response headers back
proxy_pass_request_headers on;
proxy_pass_header WWW-Authenticate;
proxy_pass_header Authorization;

proxy_ssl_server_name on;

# ---------- Health probe ----------
location = / {
    return 200 "ok";
    add_header Content-Type text/plain;
}

NTLM-backed services

In the same Advanced → Custom Nginx Configuration section, added the following NTLM services.

# ---------- NTLM-backed services ----------
# Autodiscover
location ~* ^/(autodiscover|Autodiscover|AutoDiscover)/ {
  proxy_http_version 1.1;
  proxy_set_header Connection "";
  proxy_set_header Authorization $http_authorization;

  proxy_ssl_name cortana-exch04.cortanadesign.com.au;
  proxy_pass https://exch_ntlm;
}

# EWS
location /ews/ {
  proxy_http_version 1.1;
  proxy_set_header Connection "";
  proxy_set_header Authorization $http_authorization;

  proxy_ssl_name cortana-exch04.cortanadesign.com.au;
  proxy_pass https://exch_ntlm;
}

# EWS MRS Proxy
location = /ews/mrsproxy.svc {
  proxy_http_version 1.1;
  proxy_set_header Connection "";
  proxy_set_header Authorization $http_authorization;

  proxy_ssl_name cortana-exch04.cortanadesign.com.au;
  proxy_pass https://exch_ntlm;
}

# MAPI over HTTP
location /mapi/ {
  proxy_http_version 1.1;
  proxy_set_header Connection "";
  proxy_set_header Proxy-Support "Session-Based-Authentication";
  proxy_set_header Authorization $http_authorization;

  proxy_ssl_name cortana-exch04.cortanadesign.com.au;
  proxy_pass https://exch_ntlm;
}

# Outlook Anywhere RPC
location /rpc {
  proxy_http_version 1.1;
  proxy_set_header Connection "";
  proxy_set_header Authorization $http_authorization;

  proxy_ssl_name cortana-exch04.cortanadesign.com.au;
  proxy_pass https://exch_ntlm;
}

# ActiveSync
location /Microsoft-Server-ActiveSync {
  proxy_http_version 1.1;
  proxy_set_header Connection "";
  proxy_set_header Authorization $http_authorization;

  proxy_ssl_name cortana-exch04.cortanadesign.com.au;
  proxy_pass https://exch_ntlm;
}

# OAB
location /oab {
  proxy_http_version 1.1;
  proxy_set_header Connection "";
  proxy_set_header Authorization $http_authorization;

  proxy_ssl_name cortana-exch04.cortanadesign.com.au;
  proxy_pass https://exch_ntlm;
}

OWA and ECP (HMA pass-through)

And lastly with OWA and ECP just pass-through without NTLM headers:

location /owa/ {
  proxy_set_header Host owa.cortanadesign.com.au;
  proxy_set_header Authorization ""; 
  proxy_ssl_name cortana-exch04.cortanadesign.com.au;
  proxy_redirect off; # preserve Location headers from Exchange
  proxy_pass https://cortana-exch04.cortanadesign.com.au;
}

location /ecp/ {
  proxy_set_header Host owa.cortanadesign.com.au;
  proxy_set_header Authorization "";
  proxy_ssl_name cortana-exch04.cortanadesign.com.au;
  proxy_redirect off; # preserve Location headers from Exchange
  proxy_pass https://cortana-exch04.cortanadesign.com.au;
}

Key points here:

  • The Host header is set to the public name (owa.cortanadesign.com.au), which must match your Exchange virtual directory URLs.
  • Authorization headers are stripped, ensuring NTLM is not passed to OWA/ECP.
  • proxy_ssl_name ensures the backend TLS handshake uses the internal server name.

Testing

  • Go to https://yourpublic.domain.com/owa which for me is https://owa.cortanadesign.com.au/owa. You should now see the Microsoft 365 sign-in page, not the old OWA forms login or an NTLM dialog box.
  • Accessing /ecp should also redirect through Entra ID, allowing logon with synced accounts.
  • Outlook desktop clients and ActiveSync on mobile devices should still connect via NTL with Autodiscover.

Troubleshooting

  • Redirect loops → check that OWA/ECP URLs in Exchange match your public DNS, and confirm reply URLs in Entra ID.
  • 400/502 errors → ensure you are using the greetoz fork with NTLM support; stock NPM will fail.
  • Blank page after login → legacy authentication is still enabled on your Exchange directories.
  • NTLM browser dialog for OWA/ECP → you skipped enabling Hybrid Modern Authentication.

Want better logging?

If you want to see what NTLM is doing behind the scenes, you can add a custom log format in NGINX Proxy Manager. Edit once again /opt/nginxproxymanager/data/nginx/custom/http_top.conf and add:

log_format ntlm_log '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_user_agent" '
                    'NTLM="$http_authorization"';

Then in your NTLM-backed locations (e.g. /ews, /rpc, /mapi, /autodiscover, /Microsoft-Server-ActiveSync, /oab) add:

access_log /var/log/nginx/ntlm_access.log ntlm_log;
error_log  /var/log/nginx/ntlm_error.log warn;

Reload NGINX Proxy Manager and tail the log to watch NTLM handshakes:

docker exec -it nginxproxymanager_app_1 tail -f /var/log/nginx/ntlm_access.log

This makes it much easier to debug Outlook and ActiveSync connection issues.

Summary

Getting Exchange 2019 to work behind NGINX Proxy Manager is possible, but not with the stock Docker image. The greetoz fork provides that handy NTLM support, which keeps Outlook and ActiveSync working. Then by following Alitajran’s HMA guide and applying the right overrides in NPM, you can also modernise OWA and ECP with OAuth, MFA, and conditional access.

Applying this approach should give you:

  • Modern login for OWA/ECP through Microsoft Entra ID.
  • NTLM passthrough for Outlook and mobile clients.
  • A clean solution managed entirely through NGINX Proxy Manager without needing to pay for NGINX Plus. $$$ Saved.

For a home environment or small business setup like mine, this strikes the right balance of simplicity, security, and functionality.