Docker - Configure your localhost to use SSL
Configure your Docker localhost to use SSL (HTTPS) without browser warnings. This step-by-step guide uses mkcert to generate trusted certificates for Apache, Nginx, and PHP containers.
Changelog
openssl with mkcert for trusted local certificates covering both localhost and 127.0.0.1; removed the obsolete Chrome flags workaround; added Windows/WSL2 CA import instructions.
This guide adds SSL to a Dockerized Apache site so https://localhost works without browser warnings. Instead of a self-signed OpenSSL certificate, it uses mkcert to generate a locally-trusted certificate covering both localhost and 127.0.0.1. Bonus sections cover the same setup for nginx and PHP, plus how to import the mkcert root CA into Windows when your browser runs outside WSL2.
In a previous article, I've explained how to run a static HTML site in seconds.
The result was a site running on your computer; using http. Let's go one step further and learn how to configure Docker to use https i.e. SSL and encryption.
In this article, you'll learn how to use Apache, nginx and PHP on your machine and be able to start https://localhost.
Once HTTPS works locally, the Apache .htaccess file article has the directives to force it in production (with HSTS preloading). And for encrypting files rather than traffic, see Encrypt sensitive data using SSL and Docker.
Our main objective is to use a Docker Apache image to enable access to localhost using either http or https. To do this, we need an SSL certificate.
We'll use mkcert, a tool that creates a local Certificate Authority (CA) and issues certificates signed by it. Once the CA is installed in your browser's trust store, https://localhost works without any security warning — and curl works without --insecure.
Then, in the Bonus section, we'll do the same for nginx and for PHP.
Let's start with just http
First things first, let's create our sandbox i.e. a small site that we will use for this article.
After a quick search on github.com, I've found a nice free one page html5/css3 template: https://github.com/peterfinlan/Sedna.
Create a temporary folder and download a sample static site
Let's download the Sedna demo site in a temporary folder on our hard disk, unzip the file, rename the default folder name Sedna-master to src and run the website using Docker:
Once these commands have been fired, please jump to http://localhost:80 and you'll get this:

Crazy easy, no?
The site is running using the http protocol but https is not yet possible. So, let's continue...
Right now, please remove the running container; we'll create it back later on:
Creation of some files we will need
As we've seen, we can run docker run -d --name static-site -p 80:80 -v ./src:/usr/local/apache2/htdocs httpd:2.4 and bingo, the site is running with http.
We can't use this single line for https because, among other things, we need to use an SSL certificate and we need to configure Apache to use it.
To make things clean and maintainable, let's create some files.
Make sure you're in the /tmp/https_localhost folder and create the following files.
FROM httpd:2.4
COPY httpd/my-site.conf /etc/apache2/sites-available/my-site.confThis Dockerfile creates a web server image based on Apache HTTP Server version 2.4, then copies a custom site configuration file into the container so Apache knows how to serve your website.
The second file we'll need should be called compose.yaml with the following content:
services: apache: build: context: . ports: - "80:80" volumes: - ./src:/usr/local/apache2/htdocsThis is a Docker Compose configuration that defines an Apache web server service. It builds the service from the current directory, exposes port 80, and mounts the local src folder into Apache's document root so that changes to your source code are immediately visible in the container.
compose.yaml is strictly equivalent to docker-compose.yml
The third file we'll need should be created in a httpd directory and has to be called my-site.conf with the following content:
<VirtualHost *:80> DocumentRoot /usr/local/apache2/htdocs ServerName localhost
<Directory /usr/local/apache2/htdocs> AllowOverride All RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ index.html [QSA,L] </Directory>
ErrorLog /usr/local/apache2/logs/error.log CustomLog /usr/local/apache2/logs/access.log combined
</VirtualHost>This Apache web server configuration sets up a virtual host on port 80 that serves files from a specific directory. It enables URL rewriting to route all requests that don't match actual files to index.html, which is a common pattern for single-page applications. It also configures where to write error and access logs.
For a container based on Apache, the website should be copied in folder /usr/local/apache2/htdocs and not, /var/www/html; that one is for a PHP image.
Everything is now in place to enable http access. Still in the /tmp/https_localhost folder, now, just run docker compose up -d --build to build our custom Docker image and run a container i.e. start the website.
You'll get something like this in the console:

If we take a look on Docker Desktop, list of containers, we'll see our project (called https_localhost since it's the name of our folder in this blog post) and we'll see our apache service running on port 80.

By accessing http://localhost:80 with the browser, we'll get our site, up and running:

Okay, so now we have confirmation that the three files we created above work and allow us http access. Let's go further with https.
Let's begin the journey to https
We have three main actions to take:
- Create our SSL certificate,
- We'll need to teach Apache how to deal with our certificate and
- We'll need to update a few our Docker files.
Create the SSL certificate
Still in our /tmp/https_localhost folder, please create a directory called ssl and we'll create our certificate there.
First, install mkcert if you haven't already:
Prerequisite: mkcert
Verify:
Then generate the local CA and the certificate:
mkcert -install creates a local CA and registers it in your system's trust store. mkcert then issues server.crt and server.key — both files land directly in the ssl/ folder.
The certificate covers both localhost (DNS) and 127.0.0.1 (IP), so accessing the site by either address works without a browser warning.
Valid for two years
Certificates generated by mkcert are valid for two years. To renew, simply re-run the mkcert command.
For an Apache image, files must be named like that, i.e. server.crt and server.key
As explained on https://hub.docker.com/_/httpd, the two files should be named like that, server.crt and server.key.
Updating our Apache configuration file
We need to update our Apache configuration file and add a Virtual host for port 443 i.e. the standard one for SSL.
Please edit the httpd/my-site.conf existing file and add the content below.
<VirtualHost *:80> DocumentRoot /usr/local/apache2/htdocs ServerName localhost
<Directory /usr/local/apache2/htdocs> AllowOverride All RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ index.html [QSA,L] </Directory>
ErrorLog /usr/local/apache2/logs/error.log CustomLog /usr/local/apache2/logs/access.log combined
</VirtualHost>
// highlight-next-line<VirtualHost *:443> // highlight-next-line DocumentRoot /usr/local/apache2/htdocs // highlight-next-line ServerName localhost // highlight-next-line
// highlight-next-line SSLEngine on // highlight-next-line SSLCertificateFile "/usr/local/apache2/conf/server.crt" // highlight-next-line SSLCertificateKeyFile "/usr/local/apache2/conf/server.key" // highlight-next-line
// highlight-next-line <Directory /usr/local/apache2/htdocs> // highlight-next-line AllowOverride All // highlight-next-line RewriteEngine On // highlight-next-line RewriteCond %{REQUEST_FILENAME} !-f // highlight-next-line RewriteRule ^(.*)$ index.html [QSA,L] // highlight-next-line </Directory> // highlight-next-line
// highlight-next-line ErrorLog /usr/local/apache2/logs/error.log // highlight-next-line CustomLog /usr/local/apache2/logs/access.log combined // highlight-next-line
// highlight-next-line</VirtualHost>This Apache configuration sets up two virtual hosts for the same website: one for unencrypted HTTP traffic on port 80, and another for encrypted HTTPS traffic on port 443. Both are configured to serve files from the same directory and use URL rewriting to route all non-file requests to index.html, which is a common pattern for single-page applications.
Paths are crucial!
Folder and file names are of major importance. The two certificate files should be saved in folder /usr/local/apache2/conf/ and be named server.crt and server.key. This because we're using an Apache Docker image; it's not the same if you're using, f.i., a PHP+Apache image.
Updating our compose.yaml file
Please edit the existing compose.yaml and add the new line below highlighted
services: apache: build: context: . ports: - "80:80" # highlight-next-line - "443:443" volumes: - ./src:/usr/local/apache2/htdocsThis is a Docker Compose configuration that sets up an Apache web server service. It builds an image from the current directory, exposes ports 80 and 443 for HTTP and HTTPS traffic, and mounts a local source directory into the container so Apache serves files from there.
So we just need to expose the port 443 from the container to our host. That port is the standard one for the https protocol.
And, finally, updating our Dockerfile file
The last file we need to update is our existing Dockerfile with the new lines below:
# cspell:disableFROM httpd:2.4# highlight-next-line
# highlight-next-lineRUN apt-get update \ # highlight-next-line && apt-get install -y --no-install-recommends apache2-utils libapache2-mod-php openssl \ # highlight-next-line && rm -rf /var/lib/apt/lists/*# highlight-next-line
# highlight-next-lineCOPY ssl/server.crt /usr/local/apache2/conf/server.crt# highlight-next-lineCOPY ssl/server.key /usr/local/apache2/conf/server.key# highlight-next-line
COPY httpd/my-site.conf /etc/apache2/sites-available/my-site.conf# highlight-next-line
# highlight-next-lineRUN sed -i \ # highlight-next-line -e 's/^#\(Include .*httpd-ssl.conf\)/\1/' \ # highlight-next-line -e 's/^#\(LoadModule .*mod_ssl.so\)/\1/' \ # highlight-next-line -e 's/^#\(LoadModule .*mod_socache_shmcb.so\)/\1/' \ # highlight-next-line /usr/local/apache2/conf/httpd.conf# highlight-next-line
# highlight-next-lineRUN a2enmod ssl \ # highlight-next-line && a2enmod rewrite \ # highlight-next-line && a2dissite 000-default default-ssl \ # highlight-next-line && a2ensite my-siteThis Dockerfile creates a web server image based on Apache HTTP Server 2.4, installs necessary tools and PHP support, configures SSL certificates for secure connections, and enables the required Apache modules to run a custom site configuration with SSL and URL rewriting capabilities.
Here we need to do a lot of stuff. We have to install (apt-get install) some Linux binaries to allow later use of a2enmod and the SSL protocol.
We need to copy our SSL certificate and private key (files server.crt and server.key) in the right place.
We should also update the default httpd.conf file of Apache to include some files and some modules.
Finally, we should enable SSL and the mod rewrite of Apache, we've to disable the standard sites (000-default and default-ssl) and to enable ours (called my-site).
Ready to run the site using both http and https
At this stage, you should have a folder structure like this:
- A folder called
httpdcontaining a file calledmy-site.confthe configuration file for Apache, - A folder called
srcwith the HTML static website files (plenty of files; coming from Github), - A folder called
sslwith the SSL certificate we've created (a public certificate calledserver.crtand a private key calledserver.key), - A file called
compose.yamland - A file called
Dockerfile
Run the site
We need to update our Docker image since we've updated the Dockerfile so we'll run docker compose up -d --build to do this.
If we go back to Docker Desktop, we should see this:

We've enabled our site to be able to run on port 80 (http) and 443 (https).
Now that everything is in place, just go to https://localhost/ and you'll get the website:

Because we used mkcert -install earlier, the browser already trusts the local CA that signed our certificate — so the padlock is green and there's no security warning.
Using Windows or WSL2?
mkcert -install registers the CA in the Linux system store. If your browser runs on Windows, you also need to import the root CA into Windows. See the Bonus section below for instructions.
Be careful with file and folder names
I spent many hours — too many — writing this article. It looked simple; there are several articles here and there that give the impression it would be easy, but it was not.
That's because the majority of examples I found used a PHP or Nginx image and not Apache. File and folder names are not the same, and we should carefully use the ones required by Apache. It was not explicit at all.
Using curl
Because mkcert -install registered the local CA in the system trust store, curl works without any extra flag:
If you're on a machine where the CA is not installed (or you skipped mkcert -install), you'll get:
In that case you can either pass the CA explicitly or use --insecure as a quick workaround:
Bonus - Configure nginx to use SSL
If you don't want Apache but nginx, please use files below instead.
It's basically the same but you'll notice some differences like in paths and the fact we don't need to install extra dependencies for PHP.
FROM nginx:latest
RUN apt-get update \ && apt-get install -y --no-install-recommends openssl \ && rm -rf /var/lib/apt/lists/*
COPY ssl/server.crt /etc/nginx/ssl/server.crtCOPY ssl/server.key /etc/nginx/ssl/server.key
COPY httpd/my-site.conf /etc/nginx/conf.d/my-site.confThis Dockerfile creates a web server image based on nginx, installs the openssl tool for SSL/TLS support, and then copies in SSL certificate files and a custom nginx configuration file so the server can handle HTTPS traffic with the specified settings.
The second file we'll need should be called compose.yaml with the following content:
services: nginx: build: context: . ports: - "80:80" - "443:443" volumes: - ./src:/usr/share/nginx/htmlThis is a Docker Compose configuration that defines an nginx web server service. It builds the nginx image from the current directory, exposes HTTP and HTTPS ports to the host machine, and mounts a local source directory into the container so nginx serves files from your project.
compose.yaml is strictly equivalent to docker-compose.yml
The third file we'll need should be created in a httpd directory and has to be called my-site.conf with the following content:
server { listen 80;
server_name localhost;
root /usr/share/nginx/html;}
server { listen 443 ssl; listen [::]:443 ssl;
server_name localhost;
ssl_certificate /etc/nginx/ssl/server.crt; ssl_certificate_key /etc/nginx/ssl/server.key;
root /usr/share/nginx/html; }This Nginx configuration sets up two web servers on the same machine: one listening on standard HTTP port 80, and another listening on HTTPS port 443 with SSL encryption enabled. Both servers serve files from the same directory and respond to requests for localhost.
Bonus - Configure PHP to use SSL
If you don't want Apache but PHP (because you've to execute some PHP code), please use files below instead.
It's basically the same but you'll notice some differences like in paths and the fact we don't need to install extra dependencies for PHP.
Please create a file called Dockerfile with the following content:
# cspell:disable
FROM php:8.3-apache
COPY ssl/server.crt /etc/ssl/certs/server.crtCOPY ssl/server.key /etc/ssl/private/server.key
COPY httpd/my-site.conf /etc/apache2/sites-available/my-ssl.conf
RUN a2enmod ssl && \ a2enmod rewrite && \ a2dissite 000-default default-ssl && \ a2ensite my-sslThis Dockerfile sets up a PHP 8.3 web server with Apache, installing SSL certificates and a custom site configuration, then enabling the necessary Apache modules and disabling default sites in favor of a custom HTTPS-enabled site.
The second file we'll need should be called compose.yaml with the following content:
services: php: build: context: . ports: - "80:80" - "443:443" volumes: - ./src:/var/www/html/This is a Docker Compose configuration that defines a PHP service. It builds a Docker image from the current directory, exposes ports 80 and 443 for web traffic, and mounts the local src folder into the container so code changes are reflected immediately.
compose.yaml is strictly equivalent to docker-compose.yml
The third file we'll need should be created in a httpd directory and has to be called my-site.conf with the following content:
<VirtualHost *:80> DocumentRoot /var/www/html ServerName localhost
<Directory /var/www/html> AllowOverride All RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ index.html [QSA,L] </Directory>
ErrorLog /var/log/apache2/error.log CustomLog /var/log/apache2/access.log combined
</VirtualHost>
<VirtualHost *:443> DocumentRoot /var/www/html ServerName localhost
SSLEngine on SSLCertificateFile "/etc/ssl/certs/server.crt" SSLCertificateKeyFile "/etc/ssl/private/server.key"
<Directory /var/www/html> AllowOverride All RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ index.html [QSA,L] </Directory>
ErrorLog /var/log/apache2/error.log CustomLog /var/log/apache2/access.log combined
</VirtualHost>This Apache configuration sets up two virtual hosts for the same website—one over regular HTTP (port 80) and one over secure HTTPS (port 443). Both are configured to serve files from /var/www/html and use URL rewriting to send all requests that don't match actual files to index.html, which is a common pattern for single-page applications.
Bonus - Install the mkcert root CA in your browser
When you run mkcert -install, the local CA is added to the trust store of your current OS. That is enough for Linux-native browsers (e.g., Firefox or Chrome running directly in Ubuntu).
If your browser runs on Windows — including when you work inside WSL2 or a Docker devcontainer — you need to import the root CA into the Windows certificate store too. You only have to do this once per machine.
Find the root CA file
rootCA.pem is the public certificate of your local CA. Keep rootCA-key.pem private — anyone holding it can sign certificates that your system will trust.
Import into Windows (from WSL2 or PowerShell)
From WSL2, you can use the Windows certutil.exe directly (WSL2 has access to Windows executables):
From a PowerShell terminal running as Administrator:
Or manually: double-click on rootCA.pem → Install Certificate → Local Machine → Place all certificates in the following store → Trusted Root Certification Authorities.
After importing, restart Chrome or Edge and https://localhost (or https://127.0.0.1) will show a green padlock with no warning.
Linux system trust store only
If you only need curl or other command-line tools to trust the CA (without installing it in Windows), copy the root CA to the system store:
The file must have the .crt extension when placed in /usr/local/share/ca-certificates
After this, curl https://localhost works without any extra flag.
