• Skip to primary navigation
  • Skip to main content
  • Skip to primary sidebar
  • Skip to footer

Mister PKI

SSL Certificates * SSL Tools * Certificate Decoder

  • PKI Training
  • OpenSSL
  • Keytool
  • PKI Tools
    • Apereo CAS
    • SAML
    • OpenID Connect
  • Get expert help

Apache mod_auth_mellon: Configure SAML SSO & Docker

September 22, 2026 by Mister PKI Leave a Comment

Overview

Many modern applications support OpenID Connect natively.

Plenty of others do not.

You may have an application that:

  • supports only trusted HTTP headers;
  • has no SAML implementation;
  • has no enterprise SSO support at all;
  • cannot be modified;
  • runs inside a container;
  • is already deployed behind Apache HTTP Server.

In that situation, Apache can act as the authentication layer in front of the application.

The architecture looks like:

Browser
   |
   | HTTPS
   v
Apache HTTP Server
+ mod_auth_mellon
   |
   | SAML 2.0
   +---------------------> Identity Provider
   |
   | authenticated request
   v
Backend Application

Apache and mod_auth_mellon act as the SAML Service Provider, or SP.

The upstream identity system acts as the Identity Provider, or IdP.

The backend application does not need to understand SAML at all.

Instead, Apache can:

  1. intercept unauthenticated requests;
  2. redirect the browser to the IdP;
  3. receive and validate the SAML response;
  4. establish a Mellon session;
  5. expose selected SAML attributes;
  6. reverse proxy the authenticated request to the application.

This is especially useful for legacy applications or containerized applications that already trust authentication headers from a reverse proxy.

This guide covers both:

  • a traditional Apache installation;
  • a Docker deployment where Apache and mod_auth_mellon run as a dedicated SAML reverse-proxy container.

The examples use generic hostnames and identities throughout.


What is mod_auth_mellon?

mod_auth_mellon is an Apache HTTP Server authentication module that implements a SAML 2.0 Service Provider.

The actively maintained project is the Latchset mod_auth_mellon project.

Once loaded into Apache, Mellon can protect URLs using directives such as:

MellonEnable auth

and communicate with a SAML Identity Provider using metadata.

A typical Mellon deployment requires four important artifacts:

SP metadata
SP private key
SP certificate
IdP metadata

plus Apache configuration.

Conceptually:

                 SAML metadata exchange
      +--------------------------------------+
      |                                      |
      v                                      |
+-------------+                       +-------------+
| Apache / SP |                       |     IdP     |
|   Mellon    |                       |             |
+-------------+                       +-------------+
      |
      v
 Web Application

Real-world scenario

Assume you have an internal application running at:

http://application:8080

inside Docker.

The application itself has no SAML implementation.

You want users to access it at:

https://portal.example.test

and authenticate with the organization’s SAML Identity Provider.

The desired architecture is:

                     SAML
                  +---------+
                  |         |
                  v         |
Browser ---> Apache Mellon ---> Identity Provider
                  |
                  |
                  v
          Backend Application
             :8080

Only Apache should be reachable by end users.

The application container should remain accessible only on the internal Docker network.

That detail is important because the backend may trust Apache-provided identity headers.


Why put SAML in the reverse proxy?

Suppose the application understands this:

X-Authenticated-User: user@example.test

but has no idea what this is:

<samlp:Response>

Apache becomes the translation boundary.

SAML assertion
     ↓
mod_auth_mellon
     ↓
Apache environment variables
     ↓
trusted HTTP headers
     ↓
application identity

This lets you add SAML authentication without modifying the application.

It also keeps the application’s authentication integration small.

The application only needs to trust requests arriving from Apache.


Security warning about trusted headers

This architecture is secure only when users cannot bypass Apache.

If the backend trusts:

X-Authenticated-User

then an attacker who can connect directly to the backend might simply send:

X-Authenticated-User: administrator

and impersonate another user.

The required network architecture is therefore:

Internet / Users
      |
      v
Apache + Mellon
      |
 private network
      |
      v
Application

Not:

             +------ Apache
             |
Users -------+
             |
             +------ Application

The application should normally have no published host port.


Step 1: Install Apache and mod_auth_mellon

RHEL-style systems

Install:

sudo dnf install \
  httpd \
  mod_auth_mellon \
  mod_ssl \
  openssl

Package names may vary slightly depending on distribution and enabled repositories.

Debian or Ubuntu

Install:

sudo apt update
sudo apt install \
  apache2 \
  libapache2-mod-auth-mellon \
  openssl \
  ca-certificates

Enable the required Apache modules:

sudo a2enmod \
  auth_mellon \
  ssl \
  proxy \
  proxy_http \
  headers

If the backend uses WebSockets:

sudo a2enmod proxy_wstunnel

Step 2: Create the Mellon configuration directory

On Debian-style systems:

sudo mkdir -p /etc/apache2/mellon

On RHEL-style systems you might instead use:

sudo mkdir -p /etc/httpd/saml2

For the rest of this guide:

/etc/apache2/mellon

will be used.

The directory will eventually contain:

/etc/apache2/mellon/
├── sp-key.pem
├── sp-cert.pem
├── sp-metadata.xml
└── idp-metadata.xml

Protect the private key:

sudo chmod 600 /etc/apache2/mellon/sp-key.pem

Step 3: Choose the SAML SP Entity ID

Every SAML entity needs a unique identifier.

For the Service Provider, use something stable such as:

https://portal.example.test/mellon/metadata

This is the Entity ID.

Do not confuse the Entity ID with the Assertion Consumer Service URL.

A typical Mellon configuration may use:

Entity ID:
https://portal.example.test/mellon/metadata

while the ACS becomes:

https://portal.example.test/mellon/postResponse

The exact endpoint paths are generated from the Mellon endpoint path.


Step 4: Generate the Mellon SP metadata and key pair

mod_auth_mellon packages normally include a metadata-generation utility.

Depending on the distribution, the executable may be named:

mellon_create_metadata

or:

mellon_create_metadata.sh

Check:

command -v mellon_create_metadata
command -v mellon_create_metadata.sh

Generate metadata using:

mellon_create_metadata \
  'https://portal.example.test/mellon/metadata' \
  'https://portal.example.test/mellon'

The tool creates three files:

private key
certificate
SP metadata

The generated filenames depend on the Entity ID.

Normalize them:

mv *.key  sp-key.pem
mv *.cert sp-cert.pem
mv *.xml  sp-metadata.xml

Then protect the key:

chmod 600 sp-key.pem

A metadata generator is preferable to manually writing the XML because it creates Mellon-compatible SAML endpoints and embeds the SP certificate correctly.


Step 5: Inspect the generated SP metadata

Open:

less sp-metadata.xml

You should find an EntityDescriptor resembling:

<EntityDescriptor
    entityID="https://portal.example.test/mellon/metadata">
    <SPSSODescriptor>
        ...
        <AssertionConsumerService
            Location="https://portal.example.test/mellon/postResponse" />
    </SPSSODescriptor>
</EntityDescriptor>

You will also see the public certificate represented as Base64 inside:

<ds:X509Certificate>

The private key is not included in the metadata.

That distinction is important:

SP metadata
   ↓
safe to provide to the IdP
SP private key
   ↓
must remain on the SP

Step 6: Register the SP with the Identity Provider

Provide:

sp-metadata.xml

to the SAML Identity Provider administrator.

The IdP uses the metadata to learn:

  • the SP Entity ID;
  • Assertion Consumer Service URLs;
  • Single Logout endpoints;
  • supported bindings;
  • the SP public certificate.

The IdP registration must also define which attributes are released.

For example:

mail
displayName
groups

The exact attribute names depend on the Identity Provider.


Step 7: Obtain the IdP metadata

The Identity Provider should publish metadata describing:

  • IdP Entity ID;
  • Single Sign-On services;
  • Single Logout services;
  • signing certificate;
  • supported bindings.

Download it:

curl \
  -fL \
  'https://idp.example.test/saml/metadata' \
  -o idp-metadata.xml

Do not use:

curl -k

in production merely to bypass TLS validation.

Instead, make sure the server running Mellon trusts the CA that issued the IdP HTTPS certificate.

Inspect the metadata:

less idp-metadata.xml

Look for:

<IDPSSODescriptor>

and one or more:

<SingleSignOnService>

entries.


Step 8: Create the basic Apache Mellon configuration

A minimal configuration resembles:

<Location />
    AuthType Mellon
    MellonEnable auth
    MellonEndpointPath /mellon
    MellonSPMetadataFile /etc/apache2/mellon/sp-metadata.xml
    MellonSPPrivateKeyFile /etc/apache2/mellon/sp-key.pem
    MellonSPCertFile /etc/apache2/mellon/sp-cert.pem
    MellonIdPMetadataFile /etc/apache2/mellon/idp-metadata.xml
    Require valid-user
</Location>

The critical directives are:

MellonSPMetadataFile
MellonSPPrivateKeyFile
MellonSPCertFile
MellonIdPMetadataFile

Together they establish the trust relationship between the SP and IdP.


MellonEnable auth versus info

Mellon supports different operating modes.

For protected locations:

MellonEnable auth

means authentication is required.

A configuration using:

MellonEnable info

can expose Mellon session information without forcing authentication.

A common pattern is:

<Location />
    MellonEnable info
    MellonEndpointPath /mellon
    MellonSPMetadataFile ...
    MellonSPPrivateKeyFile ...
    MellonSPCertFile ...
    MellonIdPMetadataFile ...
</Location>
<Location /secure>
    AuthType Mellon
    MellonEnable auth
    Require valid-user
</Location>

This allows selected routes to require authentication.

If the entire application requires SSO, protecting / is simpler.


Step 9: Configure the Mellon endpoint

Use:

MellonEndpointPath /mellon

Mellon creates internal SAML endpoints beneath this path.

Typical URLs include:

/mellon/metadata
/mellon/login
/mellon/logout
/mellon/postResponse

Do not reverse proxy these URLs to the backend application.

Apache itself must handle them.

If Apache also acts as the reverse proxy:

ProxyPass /mellon !

The exclamation mark tells Apache:

Do not proxy this path.

This is extremely important.

Otherwise:

/mellon/postResponse

might be forwarded to the application instead of processed by Mellon.


Step 10: Configure SAML session cookies

A practical configuration is:

MellonSecureCookie On
MellonCookieSameSite Lax

or, depending on the SAML flow and IdP behavior:

MellonCookieSameSite None

If you use:

MellonCookieSameSite None

also use:

MellonSecureCookie On

so the cookie is sent only over HTTPS.

Test the actual authentication flow in current browsers rather than choosing a SameSite value blindly.


Step 11: Select the authenticated username

Mellon makes the SAML NameID available as:

NAME_ID

The default username source is commonly:

MellonUser "NAME_ID"

Explicitly configure it:

MellonUser "NAME_ID"

After authentication, Apache can expose:

MELLON_NAME_ID

for use by modules and applications.

If the IdP sends an email-style NameID:

user@example.test

that can become the authenticated application identity.

Alternatively, you can select another SAML attribute.

For example:

MellonUser "uid"

The IdP must actually release that attribute.


Step 12: Require an attribute

You can require conditions beyond merely:

Require valid-user

Mellon provides authorization directives such as:

MellonCond

For example:

MellonCond "department" "^Engineering$" [REG]

or:

MellonCond "NAME_ID" ".+" [REG]

This lets Apache reject a SAML-authenticated user who does not satisfy required attributes.

Keep authorization rules simple unless Apache is genuinely intended to be the authorization layer.

In many architectures:

Mellon
    ↓
Authentication
Application
    ↓
Authorization

is easier to maintain.


Step 13: Map SAML attributes to Apache environment variables

Assume the IdP releases:

mail
displayName
memberOf

Map them with:

MellonSetEnv "email" "mail"
MellonSetEnv "displayName" "displayName"
MellonSetEnv "groups" "memberOf"

By default Mellon prefixes generated environment variables with:

MELLON_

so these become approximately:

MELLON_email
MELLON_displayName
MELLON_groups

The maintained Mellon configuration examples document MellonSetEnv specifically for mapping incoming IdP attributes to local Apache environment-variable names.


Step 14: Understand multi-valued SAML attributes

This becomes important with group membership.

Suppose the IdP returns:

groups:
  app-users
  app-editors
  app-admins

Without attribute merging, Mellon may expose these as indexed environment variables such as:

MELLON_groups
MELLON_groups_0
MELLON_groups_1
MELLON_groups_2

The Mellon documentation notes that:

MellonMergeEnvVars

can combine multi-valued attributes into one variable.

For example:

MellonMergeEnvVars On ";"

could produce:

MELLON_groups=app-users;app-editors;app-admins

The default merge separator is a semicolon; Mellon permits specifying another separator.

Choose the delimiter according to what the backend expects.


Avoid delimiters that occur inside group values

Suppose directory memberships arrive as full distinguished names:

CN=app-users,OU=Groups,DC=example,DC=test
CN=app-admins,OU=Groups,DC=example,DC=test

Using:

MellonMergeEnvVars On ","

would create:

CN=app-users,OU=Groups,DC=example,DC=test,CN=app-admins,...

Now it is impossible for a downstream application to distinguish:

group separator commas

from:

LDAP DN commas

Prefer either:

  1. having the IdP release simplified application-specific group names; or
  2. using a delimiter that cannot appear in the source values.

The better architecture is often:

Directory groups
       ↓
IdP filters/transforms
       ↓
app-users
app-admins
       ↓
SAML
       ↓
Mellon

rather than sending every raw LDAP DN to the application.


Step 15: Reverse proxy the application

Enable proxying:

ProxyPreserveHost On
ProxyPass /mellon !
ProxyPass        / http://application:8080/
ProxyPassReverse / http://application:8080/

The order matters conceptually:

/mellon
    ↓
handled by Apache
everything else
    ↓
proxied to backend

Apache’s mod_proxy acts as the reverse proxy and can preserve the original host with ProxyPreserveHost.


Step 16: Pass authenticated identity to the backend

Suppose the application trusts:

X-Authenticated-User
X-Authenticated-Email
X-Authenticated-Groups

Configure:

RequestHeader set X-Authenticated-User \
    "%{MELLON_NAME_ID}e" \
    "expr=-n %{env:MELLON_NAME_ID}"
RequestHeader set X-Authenticated-Email \
    "%{MELLON_email}e" \
    "expr=-n %{env:MELLON_email}"
RequestHeader set X-Authenticated-Groups \
    "%{MELLON_groups}e" \
    "expr=-n %{env:MELLON_groups}"

The:

e

means the value comes from an Apache environment variable.

This translates:

SAML NameID
        ↓
MELLON_NAME_ID
        ↓
X-Authenticated-User

for the backend.


Step 17: Strip incoming authentication headers first

Never simply do this:

RequestHeader set X-Authenticated-User "%{MELLON_NAME_ID}e"

without considering client input.

A malicious request might arrive containing:

X-Authenticated-User: administrator

Strip the trusted identity headers first:

RequestHeader unset X-Authenticated-User
RequestHeader unset X-Authenticated-Email
RequestHeader unset X-Authenticated-Groups

Then set them from Mellon:

RequestHeader set X-Authenticated-User \
    "%{MELLON_NAME_ID}e" \
    "expr=-n %{env:MELLON_NAME_ID}"

The trust boundary becomes:

Client header
     ↓
removed by Apache
SAML identity
     ↓
Mellon environment
     ↓
new trusted header

This is one of the most important security controls when putting SAML in front of a trusted-header application.


Step 18: Complete traditional Apache virtual host

A simplified complete configuration might look like:

<VirtualHost *:443>
    ServerName portal.example.test
    SSLEngine On
    SSLCertificateFile /etc/apache2/tls/server.crt
    SSLCertificateKeyFile /etc/apache2/tls/server.key
    SSLProtocol -all +TLSv1.2 +TLSv1.3
    ErrorLog ${APACHE_LOG_DIR}/portal-error.log
    CustomLog ${APACHE_LOG_DIR}/portal-access.log combined
    <Location />
        AuthType Mellon
        MellonEnable auth
        MellonEndpointPath /mellon
        MellonSPMetadataFile \
            /etc/apache2/mellon/sp-metadata.xml
        MellonSPPrivateKeyFile \
            /etc/apache2/mellon/sp-key.pem
        MellonSPCertFile \
            /etc/apache2/mellon/sp-cert.pem
        MellonIdPMetadataFile \
            /etc/apache2/mellon/idp-metadata.xml
        MellonSecureCookie On
        MellonCookieSameSite Lax
        MellonUser "NAME_ID"
        MellonSetEnv "email" "mail"
        MellonSetEnv "displayName" "displayName"
        MellonSetEnv "groups" "groups"
        MellonMergeEnvVars On ";"
        Require valid-user
    </Location>
    ProxyPass /mellon !
    RequestHeader unset X-Authenticated-User
    RequestHeader unset X-Authenticated-Email
    RequestHeader unset X-Authenticated-Groups
    RequestHeader set X-Authenticated-User \
        "%{MELLON_NAME_ID}e" \
        "expr=-n %{env:MELLON_NAME_ID}"
    RequestHeader set X-Authenticated-Email \
        "%{MELLON_email}e" \
        "expr=-n %{env:MELLON_email}"
    RequestHeader set X-Authenticated-Groups \
        "%{MELLON_groups}e" \
        "expr=-n %{env:MELLON_groups}"
    ProxyPreserveHost On
    ProxyPass        / http://application:8080/
    ProxyPassReverse / http://application:8080/
</VirtualHost>

Adjust attribute names to whatever your IdP actually releases.


Step 19: Validate the Apache configuration

Always run:

apachectl configtest

or:

apachectl -t

Expected:

Syntax OK

Then restart:

sudo systemctl restart apache2

or on RHEL:

sudo systemctl restart httpd

Check:

sudo systemctl status apache2

and review the error log if authentication fails.


Step 20: Verify Mellon metadata from the browser

Mellon normally exposes the SP metadata endpoint beneath:

/mellon/metadata

For example:

https://portal.example.test/mellon/metadata

Retrieve it:

curl \
  -fsS \
  https://portal.example.test/mellon/metadata

Confirm the:

entityID

and endpoints match what was registered with the IdP.

A mismatch between the runtime metadata and the registered metadata is a common cause of SAML failures.


Docker architecture with Apache Mellon

Now consider the same design in Docker.

Instead of installing Apache directly on the host:

                       SAML
                   +-----------+
                   |           |
                   v           |
Browser ---> Mellon Container ---> Identity Provider
                   |
                   | Docker network
                   |
                   v
            Application Container

The Apache container handles:

TLS
SAML
trusted headers
reverse proxy

while the application container handles only the application.

This separation is particularly useful when the application has no native SAML support.


Why use a separate Mellon container?

A dedicated authentication proxy allows the application image to remain untouched.

Instead of:

Application image
+ Apache
+ SAML libraries
+ Mellon
+ certificates
+ metadata

you get:

Application image
    ↓
application only

and:

SAML proxy image
    ↓
Apache
mod_auth_mellon
TLS
SP configuration

This follows the container principle of separating distinct responsibilities.


Step 21: Create the Docker directory structure

Create:

saml-proxy/
├── Dockerfile
├── apache.conf
├── entrypoint.sh
├── mellon/
│   ├── sp-metadata.xml
│   ├── sp-cert.pem
│   ├── sp-key.pem
│   └── idp-metadata.xml
└── tls/
    ├── server.crt
    └── server.key

Do not commit:

sp-key.pem
server.key

to source control.

A .gitignore should include:

saml-proxy/mellon/sp-key.pem
saml-proxy/tls/server.key

You may choose to ignore the entire generated key directory.


Step 22: Create the Dockerfile

A Debian-based example:

FROM debian:13-slim
RUN apt-get update \
 && apt-get install -y --no-install-recommends \
      apache2 \
      libapache2-mod-auth-mellon \
      ca-certificates \
      curl \
      openssl \
 && a2enmod \
      auth_mellon \
      ssl \
      proxy \
      proxy_http \
      proxy_wstunnel \
      headers \
 && a2dissite 000-default \
 && rm -rf /var/lib/apt/lists/*
COPY apache.conf \
  /etc/apache2/sites-available/application.conf
COPY entrypoint.sh \
  /usr/local/bin/entrypoint.sh
RUN a2ensite application \
 && chmod +x /usr/local/bin/entrypoint.sh
EXPOSE 8443
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]

This installs only the components required for:

Apache
Mellon
TLS
proxying
metadata retrieval

The exact base-image and package versions should be pinned according to your normal container lifecycle policy.


Step 23: Create the Docker Apache configuration

Use:

Listen 8443
<VirtualHost *:8443>
    ServerName portal.example.test
    SSLEngine On
    SSLCertificateFile \
        /etc/apache2/tls/server.crt
    SSLCertificateKeyFile \
        /etc/apache2/tls/server.key
    SSLProtocol -all +TLSv1.2 +TLSv1.3
    ErrorLog /dev/stderr
    CustomLog /dev/stdout combined
    <Location />
        AuthType Mellon
        MellonEnable auth
        MellonEndpointPath /mellon
        MellonSPMetadataFile \
            /etc/apache2/mellon/sp-metadata.xml
        MellonSPPrivateKeyFile \
            /etc/apache2/mellon/sp-key.pem
        MellonSPCertFile \
            /etc/apache2/mellon/sp-cert.pem
        MellonIdPMetadataFile \
            /etc/apache2/mellon/idp-metadata.xml
        MellonSecureCookie On
        MellonCookieSameSite Lax
        MellonUser "NAME_ID"
        MellonSetEnv "email" "mail"
        MellonSetEnv "groups" "groups"
        MellonMergeEnvVars On ";"
        Require valid-user
    </Location>
    ProxyPass /mellon !
    RequestHeader unset X-Authenticated-User
    RequestHeader unset X-Authenticated-Email
    RequestHeader unset X-Authenticated-Groups
    RequestHeader set X-Authenticated-User \
        "%{MELLON_NAME_ID}e" \
        "expr=-n %{env:MELLON_NAME_ID}"
    RequestHeader set X-Authenticated-Email \
        "%{MELLON_email}e" \
        "expr=-n %{env:MELLON_email}"
    RequestHeader set X-Authenticated-Groups \
        "%{MELLON_groups}e" \
        "expr=-n %{env:MELLON_groups}"
    ProxyPreserveHost On
    ProxyPass \
        / \
        http://application:8080/ \
        nocanon \
        timeout=3600
    ProxyPassReverse \
        / \
        http://application:8080/
</VirtualHost>

The Docker pattern is the same as traditional Apache.

The difference is that:

application

is the Docker DNS name of the backend service.


Step 24: Create the Docker entrypoint

The entrypoint can validate permissions and then start Apache:

#!/usr/bin/env bash
set -euo pipefail
MELLON_DIR="/etc/apache2/mellon"
required_files=(
    "$MELLON_DIR/sp-key.pem"
    "$MELLON_DIR/sp-cert.pem"
    "$MELLON_DIR/sp-metadata.xml"
    "$MELLON_DIR/idp-metadata.xml"
)
for file in "${required_files[@]}"; do
    if [[ ! -s "$file" ]]; then
        echo "ERROR: Missing required file: $file" >&2
        exit 1
    fi
done
chmod 600 "$MELLON_DIR/sp-key.pem"
apache2ctl configtest
exec apache2ctl -D FOREGROUND

Using:

exec

makes Apache the container’s main process so it receives container stop signals correctly.


Step 25: Create the Docker Compose stack

A simple Compose file:

services:
  application:
    image: example/application:latest
    restart: unless-stopped
    networks:
      - app_network
    # Intentionally no "ports:" section.
    #
    # Users must not be able to bypass
    # the SAML reverse proxy.
  saml-proxy:
    build:
      context: ./saml-proxy
    restart: unless-stopped
    depends_on:
      - application
    ports:
      - "443:8443"
    volumes:
      - ./saml-proxy/mellon:/etc/apache2/mellon:ro
      - ./saml-proxy/tls:/etc/apache2/tls:ro
    networks:
      - app_network
networks:
  app_network:

Notice the backend application has:

no host port

That is intentional.

Docker networking allows:

saml-proxy

to reach:

application:8080

without exposing port 8080 to users.


Step 26: Build and start the containers

Run:

docker compose build

Then:

docker compose up -d

Check:

docker compose ps

Inspect Apache logs:

docker compose logs -f saml-proxy

The proxy should listen externally while the application remains internal to the Compose network.


Step 27: Generate Mellon metadata using the container image

You do not need Mellon installed on the Docker host.

Use the image itself.

For example:

docker compose build saml-proxy

Then run the metadata utility in a temporary container.

Conceptually:

docker run \
  --rm \
  -v "$PWD/saml-proxy/mellon:/out" \
  -w /out \
  --entrypoint mellon_create_metadata \
  saml-proxy-image \
  'https://portal.example.test/mellon/metadata' \
  'https://portal.example.test/mellon'

Because Docker Compose image names vary by project, another useful pattern is:

docker compose run \
  --rm \
  --entrypoint mellon_create_metadata \
  saml-proxy \
  'https://portal.example.test/mellon/metadata' \
  'https://portal.example.test/mellon'

Afterward, normalize the generated names:

cd saml-proxy/mellon
mv *.key  sp-key.pem
mv *.cert sp-cert.pem
mv *.xml  sp-metadata.xml
chmod 600 sp-key.pem

This makes the metadata-generation process reproducible without installing packages on the host.


Step 28: Optionally download IdP metadata at container startup

Instead of storing:

idp-metadata.xml

inside the image, the container can retrieve it at startup.

Example:

IDP_METADATA_URL="${SAML_IDP_METADATA_URL:?Required}"
for attempt in $(seq 1 20); do
    if curl \
        -fsS \
        -o /etc/apache2/mellon/idp-metadata.xml \
        "$IDP_METADATA_URL"
    then
        break
    fi
    echo "Waiting for IdP metadata..."
    sleep 3
done
if [[ ! -s /etc/apache2/mellon/idp-metadata.xml ]]; then
    echo "Unable to retrieve IdP metadata" >&2
    exit 1
fi

This can be useful in disposable development environments.

For production, decide deliberately whether metadata should be:

version-controlled configuration

or:

retrieved dynamically

Dynamic metadata retrieval has tradeoffs.

An IdP metadata change could alter trusted signing certificates or endpoints without a normal application deployment.


Step 29: Development versus production SP keys

For a disposable local environment, you might generate a new SP key each time:

container starts
      ↓
generate throwaway key
      ↓
generate metadata

That can work when the test IdP is generated alongside it.

Do not use that pattern when the SP is registered with an external production IdP.

Production requires stable SP identity material:

sp-key.pem
sp-cert.pem
sp-metadata.xml

If the key changes unexpectedly, the IdP registration may no longer match the SP.

Use:

persistent secret
Docker secret
Kubernetes secret
vault
deployment pipeline

or another managed secret mechanism for the private key.


Step 30: Local SAML development with a disposable IdP

A fully self-contained development stack can include a test IdP:

Browser
   |
   v
Apache Mellon
   |
   | SAML
   v
Disposable Local IdP
   |
   v
Test Account

The Compose stack becomes:

application
saml-proxy
local-idp

This has a useful property:

Developers can test the complete SAML flow without depending on an organization’s real identity environment.

The local IdP can release generic attributes such as:

mail
displayName
groups

Example test identity:

user@example.test
displayName:
Example User
groups:
app-users
app-editors

Never copy real enterprise identities, domains, directory DNs, or group memberships into the test IdP.


Step 31: Development Compose example

Conceptually:

services:
  application:
    image: example/application:latest
    networks:
      - app_network
  saml-proxy:
    build:
      context: ./saml-proxy
    depends_on:
      - application
      - local-idp
    environment:
      SAML_IDP_METADATA_URL: >
        http://local-idp:8080/saml/metadata
    volumes:
      - ./saml-proxy/mellon:/etc/apache2/mellon
      - ./saml-proxy/tls:/etc/apache2/tls
    ports:
      - "443:8443"
    networks:
      - app_network
  local-idp:
    image: example/test-saml-idp:latest
    ports:
      - "127.0.0.1:8081:8080"
    networks:
      - app_network
networks:
  app_network:

Notice:

127.0.0.1

for the local IdP’s host binding.

That prevents a developer-only identity provider from accidentally being exposed to the network.


Step 32: TLS inside the Docker SAML proxy

The Mellon SP should normally be externally reachable over HTTPS:

https://portal.example.test

SAML metadata must therefore describe HTTPS endpoints.

For local testing, generate a temporary self-signed certificate:

openssl req \
  -x509 \
  -newkey rsa:2048 \
  -nodes \
  -keyout server.key \
  -out server.crt \
  -days 30 \
  -subj '/CN=portal.example.test' \
  -addext 'subjectAltName=DNS:portal.example.test'

For production, use a certificate issued by an appropriate CA.

For more examples, see OpenSSL Self Signed Certificate.

You can inspect a certificate with:

openssl x509 \
  -in server.crt \
  -text \
  -noout

See OpenSSL View Certificate for additional inspection commands.


Step 33: TLS termination in front of Mellon

Some architectures have another load balancer:

Browser
   |
 HTTPS
   |
   v
Load Balancer
   |
 HTTPS
   |
   v
Apache Mellon
   |
 HTTP
   |
   v
Application

This is usually easier than:

Browser
   |
 HTTPS
   |
   v
Load Balancer
   |
 HTTP
   |
   v
Apache Mellon

because Mellon constructs SAML endpoint URLs based on how Apache sees the request.

Historically, running Mellon behind an HTTPS-offloading proxy while Mellon itself receives HTTP has caused SAML destination mismatches if the SP expects http:// while the IdP posts to an externally advertised https:// URL.

If practical, re-encrypt from the load balancer to Apache so Mellon itself sees HTTPS.


Step 34: UseCanonicalName and ServerName

When Apache sits behind a reverse proxy or load balancer, stable URL construction is important.

A configuration may include:

ServerName portal.example.test
UseCanonicalName On

This helps Apache use the intended canonical hostname.

The Mellon metadata, Apache ServerName, public DNS, and load-balancer hostname should all describe the same externally visible service.

Avoid a situation like:

metadata:
https://portal.example.test/mellon/postResponse
Apache thinks:
https://container-7c4f:8443/mellon/postResponse

SAML is sensitive to exact endpoint URLs.


Step 35: WebSocket applications

Many modern web applications use WebSockets.

If the application requires them, enable:

a2enmod proxy_wstunnel

Modern Apache versions can handle WebSocket upgrades through mod_proxy_http as well, depending on configuration.

A common proxy configuration is:

ProxyPass \
    / \
    http://application:8080/ \
    nocanon \
    upgrade=websocket \
    timeout=3600

Test normal HTTP requests and WebSockets separately after introducing the SAML proxy.


Step 36: Log authenticated identities safely

During troubleshooting, temporarily log Mellon’s authenticated user:

LogFormat \
  "%h %l %{MELLON_NAME_ID}e %t \"%r\" %>s %b" \
  saml_combined
CustomLog /dev/stdout saml_combined

This can help confirm:

authentication succeeded
        ↓
Mellon session exists
        ↓
NAME_ID is populated

Do not log:

full SAML assertions
authentication secrets
sensitive attributes

in production unless there is a specific diagnostic requirement.


Step 37: Debug Mellon attributes

For temporary troubleshooting, create a protected diagnostic endpoint or increase Apache logging.

The important thing to determine is what Mellon actually receives.

For example:

MELLON_NAME_ID=user@example.test
MELLON_email=user@example.test
MELLON_groups=app-users;app-editors

If the expected value is missing, troubleshoot in this order:

1. Did the IdP release the attribute?
2. Is the attribute name correct?
3. Did Mellon receive it?
4. Did MellonSetEnv map it?
5. Did Apache set the backend header?
6. Did the application read the header?

Avoid changing all six layers simultaneously.


Step 38: Troubleshoot endless SAML redirect loops

Symptoms:

Application
   ↓
IdP
   ↓
Application
   ↓
IdP
   ↓
...

Possible causes include:

  • Mellon session cookie not being retained;
  • SameSite cookie behavior;
  • HTTP/HTTPS mismatch;
  • incorrect SP metadata;
  • load balancer rewriting hostnames;
  • multiple application nodes with inconsistent configuration.

Check:

MellonSecureCookie On

and the selected:

MellonCookieSameSite

value.

Inspect the browser’s cookies and network requests.


Step 39: Troubleshoot invalid Destination

You may see an error resembling:

Invalid Destination on Response

This commonly means:

IdP sent response to:
https://portal.example.test/mellon/postResponse

while Mellon expected something different.

Check:

SP metadata ACS URL
Apache ServerName
HTTP versus HTTPS
load-balancer forwarding
MellonEndpointPath

The URLs must agree.

SAML endpoint mismatches are exact-string problems surprisingly often.


Step 40: Troubleshoot missing attributes

Suppose authentication succeeds, but:

MELLON_email

is empty.

First inspect the SAML response using a browser SAML tracing extension.

Look for:

<saml:Attribute Name="mail">

If the IdP does not send the attribute, Apache cannot map it.

If it does exist, verify:

MellonSetEnv "email" "mail"

Remember that SAML attribute names are case-sensitive in many integrations.


Step 41: Troubleshoot only the first group appearing

This is a common Mellon issue with multi-valued attributes.

If:

groups

contains multiple values but:

MELLON_groups

contains only one, check:

MellonMergeEnvVars

Mellon’s documented default is:

Off

while indexed variables may hold additional values.

Enable merging:

MellonMergeEnvVars On ";"

Then verify:

MELLON_groups=app-users;app-editors;app-admins

Choose the separator to match the application.


Step 42: Troubleshoot 403 after successful SAML login

A SAML response may validate successfully but Apache can still deny authorization.

Check:

Require valid-user

and any:

MellonCond
MellonRequire

directives.

For example:

MellonCond "groups" "^app-users$" [REG]

may not behave as intended if:

groups

is multi-valued or has a different format.

Authentication success does not imply authorization success.


Step 43: Troubleshoot backend header spoofing

If users can connect to:

http://application:8080

directly, stop troubleshooting SAML and fix the network architecture first.

The backend should not trust headers from arbitrary clients.

In Docker Compose, remove:

ports:
  - "8080:8080"

from the application.

Use only:

networks:
  - app_network

so Apache can reach it internally.


Step 44: Health checking Mellon

A useful health check is the metadata endpoint:

curl \
  -fsSk \
  https://localhost:8443/mellon/metadata \
  > /dev/null

In Compose:

healthcheck:
  test:
    [
      "CMD-SHELL",
      "curl -fsSk -o /dev/null https://localhost:8443/mellon/metadata"
    ]
  interval: 30s
  timeout: 10s
  retries: 5
  start_period: 20s

This checks that:

Apache is running
TLS is working
Mellon loaded
SP metadata is reachable

It does not prove that the upstream IdP is functioning.

End-to-end SAML authentication requires a separate functional test.


Step 45: SAML metadata lifecycle

Treat SAML metadata as configuration that can change.

The IdP may rotate:

signing certificates

and publish the replacement through metadata.

The SP may also rotate its own:

SP signing certificate

A production process should define:

metadata refresh
certificate rollover
testing
rollback

Do not wait until the current IdP certificate expires before considering metadata lifecycle.


SP certificate versus HTTPS certificate

A Mellon deployment can have two different certificate/key pairs.

HTTPS certificate

Used by Apache TLS:

SSLCertificateFile
SSLCertificateKeyFile

Purpose:

Browser ↔ Apache TLS

SAML SP certificate

Used by Mellon:

MellonSPCertFile
MellonSPPrivateKeyFile

Purpose:

SAML signing / encryption

These are distinct roles.

Conceptually:

HTTPS
Browser ================= Apache
SAML
Apache <=================> IdP

Do not assume the same certificate must serve both purposes.

Separate keys can make lifecycle management clearer.


SAML authentication flow with Mellon

The complete SP-initiated flow looks like:

1. Browser requests protected page
           ↓
2. Mellon sees no authenticated session
           ↓
3. Apache redirects browser to IdP
           ↓
4. User authenticates at IdP
           ↓
5. IdP creates signed SAML Response
           ↓
6. Browser POSTs response to
   /mellon/postResponse
           ↓
7. Mellon validates:
   - response signature
   - issuer
   - destination
   - audience
   - timestamps
           ↓
8. Mellon creates local session
           ↓
9. Original request resumes
           ↓
10. Apache passes selected identity
    information to backend

The application never processes SAML XML.


SAML versus OIDC reverse proxying

If an application already supports OpenID Connect, native OIDC is often simpler than translating SAML through Apache.

But Mellon remains valuable when:

IdP supports SAML
        +
application does not
        +
Apache can sit in front of application

The choice is architectural:

Native OIDC available?
      |
      +--- yes ---> Prefer native application integration
      |
      +--- no ----> Apache/Mellon can add SAML externally

A reverse-proxy SAML layer is particularly useful for:

  • legacy applications;
  • vendor applications;
  • simple dashboards;
  • internally developed apps using trusted-header authentication;
  • applications where modifying authentication code is undesirable.

Apache Mellon Docker security checklist

Before production deployment, verify:

[ ] Backend application has no direct public port
[ ] Client-supplied identity headers are removed
[ ] Only Mellon-generated headers reach the application
[ ] SP private key is not committed to Git
[ ] TLS private key is not committed to Git
[ ] IdP metadata comes from a trusted source
[ ] SP metadata is registered exactly at the IdP
[ ] Entity ID is stable
[ ] ACS URLs use the externally visible HTTPS hostname
[ ] Mellon endpoint is excluded from ProxyPass
[ ] HTTPS is enforced
[ ] MellonSecureCookie is enabled
[ ] SameSite behavior is tested
[ ] SAML attributes are minimized
[ ] Group values use an unambiguous format
[ ] Multi-value attribute handling is tested
[ ] Apache configtest succeeds
[ ] Metadata endpoint works
[ ] SAML login works
[ ] Logout is tested
[ ] IdP certificate rollover is planned

Complete Docker example

Dockerfile

FROM debian:13-slim
RUN apt-get update \
 && apt-get install -y --no-install-recommends \
      apache2 \
      libapache2-mod-auth-mellon \
      ca-certificates \
      curl \
      openssl \
 && a2enmod \
      auth_mellon \
      ssl \
      proxy \
      proxy_http \
      proxy_wstunnel \
      headers \
 && a2dissite 000-default \
 && rm -rf /var/lib/apt/lists/*
COPY apache.conf \
  /etc/apache2/sites-available/application.conf
COPY entrypoint.sh \
  /usr/local/bin/entrypoint.sh
RUN a2ensite application \
 && chmod +x /usr/local/bin/entrypoint.sh
EXPOSE 8443
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]

entrypoint.sh

#!/usr/bin/env bash
set -euo pipefail
required_files=(
  /etc/apache2/mellon/sp-key.pem
  /etc/apache2/mellon/sp-cert.pem
  /etc/apache2/mellon/sp-metadata.xml
  /etc/apache2/mellon/idp-metadata.xml
  /etc/apache2/tls/server.crt
  /etc/apache2/tls/server.key
)
for file in "${required_files[@]}"; do
    if [[ ! -s "$file" ]]; then
        echo "ERROR: Missing $file" >&2
        exit 1
    fi
done
chmod 600 \
  /etc/apache2/mellon/sp-key.pem \
  /etc/apache2/tls/server.key
apache2ctl configtest
exec apache2ctl -D FOREGROUND

compose.yml

services:
  application:
    image: example/application:latest
    restart: unless-stopped
    networks:
      - application_network
  saml-proxy:
    build:
      context: ./saml-proxy
    restart: unless-stopped
    depends_on:
      - application
    ports:
      - "443:8443"
    volumes:
      - ./saml-proxy/mellon:/etc/apache2/mellon:ro
      - ./saml-proxy/tls:/etc/apache2/tls:ro
    networks:
      - application_network
networks:
  application_network:

The critical security property is visible immediately:

application:
    no ports
saml-proxy:
    port 443 exposed

Users therefore cannot bypass authentication.


Frequently asked questions

What is mod_auth_mellon?

mod_auth_mellon is an Apache HTTP Server module that implements a SAML 2.0 Service Provider.

It allows Apache to authenticate users through a SAML Identity Provider.

Can mod_auth_mellon protect an application that has no SAML support?

Yes.

Apache can authenticate the user with Mellon and then reverse proxy the request to the application.

The backend can consume an authenticated username or selected SAML attributes through trusted HTTP headers.

Can mod_auth_mellon run in Docker?

Yes.

A useful architecture is:

Apache + Mellon container
        ↓
private Docker network
        ↓
application container

Only the Apache container should normally expose a public port.

What files does Mellon need?

A normal SAML SP configuration uses:

SP metadata
SP certificate
SP private key
IdP metadata
Apache configuration

How do I generate Mellon SP metadata?

Use the metadata utility packaged with Mellon:

mellon_create_metadata \
  ENTITY_ID \
  ENDPOINT_URL

The exact executable name or filesystem location varies by Linux distribution.

What is MellonEndpointPath?

It defines the Apache path under which Mellon exposes its SAML endpoints.

For example:

MellonEndpointPath /mellon

creates endpoints underneath:

/mellon

including metadata and SAML response handlers.

What is MellonUser?

MellonUser selects the SAML value Apache treats as the authenticated username.

For example:

MellonUser "NAME_ID"

How do I map SAML attributes?

Use:

MellonSetEnv

For example:

MellonSetEnv "email" "mail"

which creates a Mellon environment variable for the incoming mail attribute.

How do I handle multiple group values?

Use:

MellonMergeEnvVars On ";"

to merge a multi-valued SAML attribute into one environment variable.

Choose a separator that does not occur inside the attribute values.

Can Mellon pass SAML groups to the application?

Yes.

For example:

MellonSetEnv "groups" "groups"
MellonMergeEnvVars On ";"
RequestHeader set \
  X-Authenticated-Groups \
  "%{MELLON_groups}e"

The backend application must understand the selected format.

Should I release LDAP memberOf directly?

Usually only if the application genuinely needs it.

A better IdP policy is often to release only application-specific groups and convert raw directory DNs into simpler values.

For example:

CN=app-users,OU=Groups,DC=example,DC=test

could become:

app-users

before reaching the SP.

Can users bypass Mellon by accessing the backend directly?

They should not be able to.

If the backend trusts authentication headers, direct backend access is an authentication bypass.

Keep the backend on a private network with no public port.

Is the Mellon certificate the same as the Apache HTTPS certificate?

Not necessarily.

The Mellon certificate is part of the SAML trust relationship.

The HTTPS certificate protects browser-to-Apache TLS.

Keeping them separate is often preferable.

Can Mellon work behind a load balancer?

Yes, but SAML endpoint URL construction must match the externally advertised HTTPS URLs.

Re-encrypting HTTPS from the load balancer to the Mellon server often avoids HTTP/HTTPS destination mismatches.

Why do I get Invalid Destination on Response?

Usually the URL in the SAML Response does not match the destination Mellon expects.

Check:

public hostname
HTTP versus HTTPS
ServerName
SP metadata
MellonEndpointPath
load balancer configuration

Why does only one group appear?

Mellon treats multi-valued attributes specially.

Check:

MellonMergeEnvVars

and the indexed environment variables Mellon produces.

Should I use SAML or OIDC?

Use the protocol supported cleanly by both your identity provider and application.

If the application already has good OIDC support, native OIDC will often be simpler.

If the application has no SSO support but can run safely behind Apache, Mellon provides a practical way to add SAML externally.


Related Mister PKI guides

  • Learn about Apereo CAS Single Logout when integrating SAML applications with CAS.
  • Create test TLS certificates with OpenSSL Self Signed Certificate.
  • Inspect the Apache or SAML SP certificate with OpenSSL View Certificate.
  • Review practical infrastructure certificate concepts in PKI for DevOps Engineers.
  • Browse additional certificate and troubleshooting commands in the OpenSSL Commands Guide.

External references

  • mod_auth_mellon maintained project
  • Keycloak mod_auth_mellon configuration guide
  • Apache mod_proxy documentation
  • Apache mod_headers documentation
  • Docker Compose documentation
  • SAML 2.0 Technical Overview

Conclusion

Apache mod_auth_mellon is a useful tool when an application needs enterprise SAML authentication but does not implement SAML itself.

The architecture is straightforward:

User
  ↓
Apache
  ↓
mod_auth_mellon
  ↔
SAML Identity Provider
  ↓
authenticated reverse proxy
  ↓
Application

The main configuration pieces are:

SP metadata
SP private key
SP certificate
IdP metadata
Mellon Apache directives

For a traditional Apache deployment, Mellon can directly protect application URLs.

For Docker, a dedicated Apache/Mellon container makes an effective authentication sidecar or reverse proxy:

Internet
   ↓
Apache + Mellon
   ↓
private Docker network
   ↓
application

The most important security rule is that the application must not be reachable through a path that bypasses Apache.

If the backend trusts:

X-Authenticated-User

then Apache is part of the application’s authentication boundary.

Strip client-supplied identity headers, recreate them only from authenticated Mellon values, and keep the backend private.

Also pay special attention to multi-valued SAML attributes such as group membership. Mellon exposes multiple attribute values differently unless MellonMergeEnvVars is enabled, and the chosen delimiter must not conflict with the values themselves.

With those controls in place, mod_auth_mellon provides a practical bridge between a SAML Identity Provider and web applications that otherwise have no SAML support.

docker,  Identity Management,  saml

Reader Interactions

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Primary Sidebar

Popular Posts

PKCS12

openssl s_client

Keytool

Keytool list

ECDSA vs RSA

OpenSSL

PKCS7

Certificate Decoder

PKI for DevOps Engineers – Free Training Series

PKI for DevOps Training Hub

Lesson 1 – How TLS and PKI Work

Lesson 2 – Understanding X.509 Certificates

Lesson 3 – Certificate Chains Explained

Lesson 4 – Debug TLS with OpenSSL

Lesson 5 – Verify Certificate Chains

Lesson 6 – Creating CSRs with OpenSSL

Lesson 7 – Working with PKCS12 Certificates

Lesson 8 – Java Keystores and keytool

Lesson 9 – Certificate Expiration Monitoring

Lesson 10 – Automating Certificate Renewal

Lesson 11 – Common TLS Errors

Lesson 12 – PKI Architecture for DevOps

Recent Posts

  • Apache mod_auth_cas: Protect Web Applications with Apereo CAS and Docker
  • Apache mod_auth_mellon: Configure SAML SSO & Docker
  • Configure OpenID Connect (OIDC) in Apereo CAS 7.3 and 8
  • Certificate Ownership Is an Operating Model Problem, Not a Tooling Problem
  • OpenSSL dgst: Create Checksums and Verify Digital Signatures

Footer

  • Twitter
  • YouTube
Mister PKI

Practical security engineering for the systems that establish trust.

Learn OpenSSL Keytool Free training
Work together PKI consulting CAS consulting Sponsored contributions
Site About Support Mister PKI Cookie policy Search
© 2026 Mister PKI Secure systems. Clear guidance.