How to Use SNI (Server Name Indication) with Apache

How to use SNI (Server Name Indication) with Apache

I still remember the days when hosting multiple HTTPS sites meant buying a separate IP address for every single domain. It was expensive and honestly kind of absurd. SNI changed all of that, and once I understood how it worked, my whole approach to SSL on shared servers changed with it. In this post, I’ll explain what SNI actually is, why it matters, and exactly how I configure it with Apache so I can serve multiple SSL certificates from a single IP address.

What Is SNI and Why It Matters

Server Name Indication (SNI) is an extension to the TLS protocol that lets a client tell the server which hostname it’s trying to reach before the SSL/TLS handshake completes. Before SNI existed, a server had no way of knowing which domain a visitor wanted until after the encrypted connection was already established — which meant a server could only present one SSL certificate per IP address.

With SNI, the browser sends the requested hostname in the initial “ClientHello” message, and the server uses that to select the right certificate. This is what makes it possible for me to host site1.com and site2.com with completely different SSL certificates, on the exact same IP, using the exact same port 443.

I rely on SNI constantly for:

  • Hosting multiple HTTPS sites on a single server
  • Reducing IP address costs, especially on cloud hosting bills
  • Supporting Let’s Encrypt certificates for many domains at once
  • Simplifying infrastructure for agencies managing dozens of client sites

Prerequisites

  • Apache 2.2.12 or newer (SNI support was added here; Apache 2.4+ is standard today and works great)
  • mod_ssl installed and enabled
  • OpenSSL installed on the server
  • At least two domains with valid SSL certificates (self-signed is fine for testing)

Step 1: Confirm mod_ssl Is Installed and Enabled

I check first:

apache2ctl -M | grep ssl

If it’s missing, I install and enable it (Debian/Ubuntu):

sudo apt update
sudo apt install -y openssl
sudo a2enmod ssl
sudo systemctl restart apache2

On CentOS/RHEL:

sudo yum install -y mod_ssl
sudo systemctl restart httpd

Step 2: Confirm Apache’s SNI Support

Modern Apache (2.4+) has SNI support built in by default when mod_ssl is enabled — I don’t need to turn it on separately. I just verify the version to be safe:

apache2 -v

Anything 2.4.x or later handles SNI out of the box.

Step 3: Get or Generate SSL Certificates for Each Domain

For real production sites, I use Let’s Encrypt via Certbot:

sudo apt install -y certbot python3-certbot-apache
sudo certbot --apache -d site1.com -d www.site1.com
sudo certbot --apache -d site2.com -d www.site2.com

Certbot automatically edits my virtual host files to add the SSL configuration and certificate paths — I always review the changes afterward.

For local testing, I generate self-signed certificates instead:

sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /etc/ssl/private/site1.key \
  -out /etc/ssl/certs/site1.crt

Step 4: Configure Separate SSL Virtual Hosts

This is where SNI actually comes to life — I define multiple <VirtualHost *:443> blocks, each with its own certificate, all sharing the same IP and port:

<VirtualHost *:443>
    ServerName site1.com
    DocumentRoot /var/www/site1.com/public_html

    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/site1.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/site1.com/privkey.pem
</VirtualHost>

<VirtualHost *:443>
    ServerName site2.com
    DocumentRoot /var/www/site2.com/public_html

    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/site2.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/site2.com/privkey.pem
</VirtualHost>

Apache uses the ServerName in combination with the SNI hostname sent by the browser to decide which block — and which certificate — to serve.

Step 5: Enable the Sites and Restart Apache

sudo a2ensite site1.com-ssl.conf
sudo a2ensite site2.com-ssl.conf
sudo apache2ctl configtest
sudo systemctl restart apache2

Step 6: Test SNI Is Working Correctly

I like to test with openssl s_client, specifying the hostname explicitly:

openssl s_client -connect yourserver-ip:443 -servername site1.com

I check the returned certificate’s subject field to confirm it matches site1.com, then repeat with -servername site2.com to confirm the second certificate loads correctly for the same IP.

I also just check both sites directly in a browser and look at the padlock/certificate details for each.

Real-World Example: Agency Hosting Dozens of Client Sites

This is honestly the scenario SNI was built for. I manage several client sites from one modestly priced VPS, each with its own Let’s Encrypt certificate, all sharing one public IP. Before SNI, this same setup would have required a dedicated IP per site — a real cost difference at scale.

Understanding What Actually Happens During the TLS Handshake

It helps me to think through the handshake sequence explicitly, because it explains why SNI works the way it does. When a browser connects to port 443, it sends a “ClientHello” message before any encryption is established. With SNI enabled, that ClientHello includes the plaintext hostname the browser is trying to reach. Apache reads that hostname, matches it against the ServerName/ServerAlias values across all its SSL-enabled virtual hosts, selects the matching certificate, and then completes the handshake using that certificate’s key pair. Only after this point does the actual encrypted connection begin, and only then does the browser send the real HTTP request with its Host header. This is why SNI has to happen before HTTPS encryption starts — the server genuinely can’t decrypt anything until it knows which certificate and key to use, and it can’t know that without SNI telling it which site was requested.

Step 7: Handling Clients That Don’t Support SNI

Even though this is now rare, I’ve occasionally had to support older embedded devices or legacy software that predates SNI. For these cases, I set up a default, non-SNI-aware virtual host as a fallback, using a single “primary” certificate that at least loads correctly for clients that can’t specify a hostname during the handshake:

<VirtualHost *:443>
    ServerName default.example.com
    SSLCertificateFile /etc/letsencrypt/live/default.example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/default.example.com/privkey.pem
</VirtualHost>

This should be the first SSL vhost defined so it becomes the fallback when a non-SNI client connects, since Apache has no hostname to route by in that scenario.

Troubleshooting Common Issues

Browser shows the wrong certificate This almost always means ServerName doesn’t match the requested hostname exactly, or the SSL vhost block ordering is off. I double-check with apache2ctl -S.

“SSL certificate problem: self-signed certificate” Expected behavior for self-signed certs in testing — browsers and tools will warn about this unless you add the cert as trusted locally.

Old clients failing to connect Some very old software (like ancient versions of Python or certain legacy Windows XP browsers) doesn’t support SNI at all. This is now extremely rare, but worth knowing if you serve legacy embedded devices.

Apache fails to start after adding SSL vhosts Run apache2ctl configtest — usually it’s a missing certificate file path or incorrect permissions on the key file.

Security Best Practices

  • I set restrictive permissions on private key files: chmod 600 and root-only ownership.
  • I enable HTTP Strict Transport Security (HSTS) headers on all SSL-enabled sites.
  • I regularly renew Let’s Encrypt certificates and set up certbot renew as a cron job or systemd timer.
  • I disable outdated protocols (SSLv3, TLS 1.0, TLS 1.1) in my SSL configuration.

Performance Optimization Tips

  • I enable OCSP stapling to reduce the SSL handshake overhead for repeat visitors.
  • I use SSLSessionCache with shmcb to speed up TLS session resumption across requests.
  • I keep certificate chains as short and clean as possible to avoid unnecessary handshake latency.

Step 8: Checking How Many Certificates a Server Is Serving

On a server hosting many SNI-based sites, I occasionally want a quick way to see all the certificates currently configured across every virtual host, mostly to audit expiry dates in bulk. I loop through my domains with a short script:

for domain in site1.com site2.com site3.com; do
  echo "== $domain =="
  echo | openssl s_client -servername $domain -connect $domain:443 2>/dev/null | openssl x509 -noout -subject -enddate
done

This gives me a fast overview of which certificate is being served for each domain and when it expires, which is especially useful right after adding a new SNI-based vhost, since it confirms the correct certificate is actually being picked up rather than accidentally falling back to a different site’s certificate due to a misconfigured ServerName.

Frequently Asked Questions

Do all browsers support SNI? Yes, virtually all modern browsers and operating systems have supported SNI for well over a decade now.

Does SNI work with wildcard certificates? Yes, and it’s actually a great combination — one wildcard cert can cover all subdomains under a single ServerName block.

Is SNI a security risk since the hostname is sent unencrypted? Historically yes — the SNI hostname was visible to network observers. Encrypted Client Hello (ECH) is the emerging standard addressing this, though it requires both client and server support.

Can I mix SNI-based virtual hosts with a default fallback? Yes, you can define a default <VirtualHost *:443> block first to catch requests that don’t match any specific ServerName.

Summary and Key Takeaways

SNI is what makes affordable, scalable HTTPS hosting for multiple domains possible on a single server. With Apache 2.4+ and mod_ssl, it works automatically — my job is really just making sure each site has its own properly configured <VirtualHost *:443> block with the correct certificate paths, and confirming everything with openssl s_client before calling it done.

References

Total
1
Shares

Leave a Reply

Previous Post
How to configure virtual hosts for SSL in Apache

How to Configure Virtual Hosts for SSL in Apache

Next Post
How to configure virtual hosts with different document roots

How to Configure Virtual Hosts with Different Document Roots

Related Posts