Setup local domain and SSL for PHP-apache container

I currently have a simple docker setup in which I’m using PHP:7.3.30-apache and I’m able to view my website at http://localhost:8000.
What is the best way to create a custom domain like “http://local-docker” instead of localhost?
In addition to this I would like to enable SSL certificates for this local domain.

I’ve found a lot of solutions that involve adding a reverse proxy but I’m not sure if this is exactly what I’m looking for.

docker-compose.yml:

version: '3.8'

services:

  php-apache-environment:
    container_name: php-apache
    build:
      dockerfile: ./.docker/php/Dockerfile
    depends_on:
      - db
    volumes:
      - ./public:/var/www/html
    ports:
      - 8000:80

  db:
    container_name: db
    image: mariadb:10.1.48
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: testje
    volumes:
      - db_data:/var/lib/mysql
      - db_conf:/etc/mysql
    ports:
      - "9906:3306"

  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    environment:
      PMA_HOST: mariadb
    ports:
        - '8080:80'
    environment:
        PMA_HOST: db
    depends_on:
        - db

# Volumes
volumes:
  db_data:
  db_conf:

Dockerfile:

FROM php:7.3.30-apache
RUN a2enmod rewrite
RUN docker-php-ext-install mysqli && docker-php-ext-enable mysqli && docker-php-ext-install pdo_mysql
RUN apt-get update && apt-get upgrade -y

You are asking for more than one thing:

1 - add a line to your computer’s host file (/etc/hosts for Linux; C:\Windows\System32\drivers\etc\hosts for Windows) pointing the desired name to 127.0.0.1, i.e. adding this hostname to the line starting with 127.0.0.1

127.0.0.1  localhost local-docker

2 - create a certificate + key matching this hostname
To create a self-signed certificate using OpenSSL only for local-docker with an expirationdate 1 year in the future you can use this command

openssl req -x509 -new -out mycert.crt -keyout mycert.key -days 365 -newkey rsa:4096 -sha256 -nodes

and answer the questions to your best knowledge. Important part is the Common Name which has to be the one you have added to your hosts-file above
Example:

Country Name (2 letter code) [AU]:DE
State or Province Name (full name) [Some-State]:Baden-Wuerttemberg
Locality Name (eg, city) []:Pforzheim
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Privat
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:local-docker
Email Address []:

3 - add certificate and enable SSL
add your key mycert.key to image’s directory /etc/ssl/private/
add your certificate mycert.crt to image’s directory /etc/ssl/certs/
You can do so either by ADDing these files to your image during creation within the Dockerfile or mount it later using the docker-compose.yml. I will use the latter option.

Add this commands to your Dockerfile to enable SSL:

a2enmod ssl && a2enmod socache_shmcb

modify /etc/apache2/sites-available/default-ssl.conf to point to your certificate + key so that the lines starting with SSLCertificate... read

SSLCertificateFile      /etc/ssl/certs/mycert.crt
SSLCertificateKeyFile /etc/ssl/private/mycert.key

You can do so by adding these commands to your docker-compose.yml

RUN sed -i '/SSLCertificateFile.*snakeoil\.pem/c\SSLCertificateFile \/etc\/ssl\/certs\/mycert.crt' /etc/apache2/sites-available/default-ssl.conf
RUN sed -i '/SSLCertificateKeyFile.*snakeoil\.key/cSSLCertificateKeyFile /etc/ssl/private/mycert.key\' /etc/apache2/sites-available/default-ssl.conf

Enable the SSL-enabled site with a2ensite default-ssl within your Dockerfile.

4 - Forward a port to container’s SSL-port
add a port of your choice to be forwarded to container’s port 443 to the php-apache-environment-section within your docker-compose.yml so that it reads (using port 8443 for SSL in this example)

  ....
  ports:
    - 8000:80
    - 8443:443
  ....

At the end my Dockerfile looks like this

FROM php:7.4-apache
RUN docker-php-ext-install mysqli && docker-php-ext-enable mysqli && docker-php-ext-install pdo_mysql
RUN a2enmod rewrite && a2enmod ssl && a2enmod socache_shmcb
RUN sed -i '/SSLCertificateFile.*snakeoil\.pem/c\SSLCertificateFile \/etc\/ssl\/certs\/mycert.crt' /etc/apache2/sites-available/default-ssl.conf && sed -i '/SSLCertificateKeyFile.*snakeoil\.key/cSSLCertificateKeyFile /etc/ssl/private/mycert.key\' /etc/apache2/sites-available/default-ssl.conf
RUN a2ensite default-ssl
RUN apt-get update && apt-get upgrade -y

and my docker-compose.yml is this (skipped the MariaDB-part because not needed for this sort-of-tutorial)

version: '3.8'

services:
    php-apache-environment:
        container_name: php-apache
        build: ./
        volumes:
          - ./mycert.crt:/etc/ssl/certs/mycert.crt
          - ./mycert.key:/etc/ssl/private/mycert.key
          - /var/www/html:/var/www/html
        ports:
          - 8000:80
          - 8443:443

Now you should be able to access your Docker-container using https://local-docker:8443 :slight_smile:

Hope this helps or at least point you into the right direction?

2 Likes

Thanks for taking your time to respond!
I’m going to try this out later today.

Does the following line enable both localhost and local-docker or does this replace localhost with local-docker?

127.0.0.1  localhost local-docker

I’m asking because I would like to have multiple websites running simultaneously in different docker containers.

Would the following line work for this purpose?

127.0.0.1  localhost local-docker local-anothersite local-anothersite2

I’m also a little confused that there is no configuration needed to point the server to the new domain other than the SSL certificate.
What if I wanted to use a custom local domain without SSL?

Hello,

editing the hosts-file is similar to setting some DNS-entries but only visible to your local computer.
So: Yes, you can add as many entries to one IP-address as you want to have.
And: This has nothing to do with SSL or not-SSL - you still can decide to access a port with http and another one with https.

Right now you can access every container with every hostname as your computer only decides which container to access based on the destination-TCP-port.
The SSL-certificate is only used like a passport presented by the webserver/container to your browser to check/ensure you are still talking to the correct host.

For the future you can do more advanced stuff - using a loadbalancer (like traefik) listening on one port :443 and decides where (which webserver/container) to send the request to based on the requested hostname.
Also you can create your own CA (certificate authority) and/or using certificates with SAN (subject alternate name = certficate is valid for multiple hostnames/ip-addresses/domains)

Did it work? I got error “this site can’t provide a secure connection”

Hello,
what is the exact errormessage?
I guess that you browser is complaining about the self-signed certificate used for encryption. You can either simply continue (with some mouse-clicks) or setup your own CA and add this to the list of trusted CAs within your browser.
Or the browser could complain about a certificate without a SAN - in the meantime using SAN is mandatory (it depends on the browser you are using).

You might want to check this discussion regarding CA/SAN:

Even though the topic is about docker itself being affected, it still applies to this case here as well.

Hi,
I followed your steps, but still my browser displays that I’m in HTTP and not HTTPS.
Certificate is created, port should be 443, I added certificate filess, I checked /etc/apache2/sites-available/default-ssl.conf file and everything seems to be ok… but I’m still not able to connect using SSL (port 443).
So what did I do wrong ?

@alainroger:
Some questions for troubleshooting:

  • What Dockerfile was used to create the image?
  • Have you checked with docker-compose logs for (error-)messages within the container?
  • Check within your docker-compose.yml that port 443 is available to the outside-world.
  • Check within your container that there is a symlink within the directory /etc/apache2/sites-enabled/ to /etc/apache2/sites-available/default-ssl.conf (should be created with the command a2ensite default-ssl)
  • Is there an error-message within your browser?
  • What Dockerfile was used to create the image?
    FROM php:5.6.40-apache

If think the main issue is with the self-signed certificate. Each browser displays information differently. While Firefox display that I added an exception, FF display https:// before the “local-docker” web root

Edge, Chrome and Brave browsers: display: NOT SECURE and strikethrough “https://” protocol

docker-compose logs
Nothing special in logs :frowning:

docker-compose.yml
Here is my docker compose file section regarding php:

php:
    restart: always
    container_name: php56
    build:
      context: ./web-server/php
      dockerfile: dockerfile_php56
    volumes:
      - ./public_html:/var/www/html
      - ./web-server/php/uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
      - ./web-server/cert/mycert.crt:/etc/ssl/certs/mycert.crt
      - ./web-server/cert/mycert.key:/etc/ssl/private/mycert.key
    ports:
      - 80:80
      - 443:443
    environment:
      - PHP_DISPLAY_ERRORS=1
      - PHP_MEMORY_LIMIT=2048M
      - PHP_MAX_EXECUTION_TIME=300
      - PHP_POST_MAX_SIZE=500M
      - PHP_UPLOAD_MAX_FILESIZE=256M
      # the next 2 lines avoid to have permissions issue as in the container owner of /var/www/html is www-data and on computer is your username
      - APACHE_RUN_USER="#1000"
      - APACHE_RUN_GROUP="#1000"

So port 443 and 80 are correctly forwarded

  • No error with browser

If it is about the self-signed-certificate you can create your own CA and use this to sign the webserver’s certificate.

For testing a self-signed-certificate might be fine, but to avoid corrersponding error-messages you need a certificate signed by a CA.

Here are some steps to create your own CA and a webserver-certificate including SAN (subject alternate name) which is mandatory since a few years.

create CA
first create a Certificate Authority. For this you have to crate a private key

openssl genrsa -aes256 -out ca-key.pem 4096

The key is named ca-key.pem and has a length of 4096 bits. The key is passwort-protected (because of the -aes256-option) and has to be kept secure as a bad guy can create/sign arbitrary certificates which are trusted by the clients.

Now that a secret key for the CA is available we need the root-certificate which has to be imported by the clients/browsers to trust the certificates issued/signed by this CA.
The root-certificate ca-root.pem is created with the following command - you may need the password for the key created in the step above:

openssl req -x509 -new -nodes -extensions v3_ca -key ca-key.pem -days 1024 -out ca-root.pem -sha512

In this case the CA will be valid 1024 days. During creation you will be asked for some attributes for the CA - an example:

Enter pass phrase for ca-key.pem:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:DE
State or Province Name (full name) [Some-State]:Baden-Wuerttemberg
Locality Name (eg, city) []:Pforzheim
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Example-Company
Organizational Unit Name (eg, section) []:IT
Common Name (e.g. server FQDN or YOUR name) []:ca.example.com
Email Address []:admin@example.com

Now you can import the ca-root.pem-file into your browser’s/computer’s truststore.

create a certificate for the webserver
As the CA is completed we can create our first certificate.
A private key is the base. Similar to the CA a private key is created:

openssl genrsa -out webserver-key.pem 4096

Adding a password is not practicable in most cases as webserver have to ask for the password at every startup.
Now we will create a CSR - some attributes will be asked. The field Common Name has to be filled with the hostname the clients will connect to (either an ip-address 192.168.2.2 or DNS-name www.example.com). You can leave the challenge-password empty:

openssl req -new -key webserver-key.pem -out webserver.csr -sha512

If I remember correctly from earlier tests the FQDN of the CA’s certificate and the FQDN of the webserver’s certificate have to be different.

Create an extfile for the webserver’s certificate which contains at least one line for alt_names (you can add multiple lines DNS.2 = ..., DNS.3 = ..., … to create a certificate valid for multiple hostnames) as newer browers don’t trust the subject-fields’s cn:

authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names

[alt_names]
DNS.1 = www.example.com

The webserver.csr can now be processed by the CA. This will create the public key for the private.key. Both (the webserver-key.pem and the webserver-pub.pem) will be needed on the webserver for encryption.
The webserver-pub.pem will be created using the following command and will be valid for 365 days:

openssl x509 -req -in webserver.csr -CA ca-root.pem -CAkey ca-key.pem -CAcreateserial -out webserver-pub.pem -days 365 -sha512 --extfile webserver.ext

The -CAcreateserial is automatically skipped if a serial-file is present which will be used in this case.
The webserver-key.pem and webserver-pub.pem can now be used within your webserver’s configuration for encryption.
Your Browsers should trust your certificate and only give some minor hint that it is signed by a CA added manually and not trusted by default.

verify that your certificate is signed correctly

openssl verify -verbose -CAfile root-ca.pem webserver-pub.pem

amazing, it works in one try , thanks Man

Hello, I have a problem, maybe you can help me. I am creating a docker on my local PC for study reasons, I followed the guide to the letter, but I get the following error and I have already searched everywhere and I cannot find a solution that helps me. The error I have is the following:

Secure connection failed

An error occurred while connecting to localhost. PR_END_OF_FILE_ERROR

If anyone can guide me I would appreciate it very much :slight_smile:

Is it related in any way to the topic title?

Hello, first of all, comment that I speak Spanish and I use a translator both to read the form and to translate my response, I hope this does not generate any misunderstanding.

That said, I think my problem is related to the title of the topic.

I am creating a docker container on my PC, locally, using docker desktop; when connecting to the server I have created from http://localhost/, it works perfectly. But if I do it through https://localhost/, I receive the error that I mentioned before and I have actually already tried everything I found and I cannot solve it, that is why I made the query, maybe you can guide me.

I followed the instructions to create the certificate and key, uploaded them to the Apache server, but something is wrong.

Then please, share what you tried. Maybe that also helps me to understand what your issue is. The error message (according to Google) is a Firefox message: How to Fix the PR_END_OF_FILE_ERROR (5 Methods)

Also share details about what you want to achieve. Do you really want to create SSL certificate for “localhost”? Don’t you have a domain? Have you tried the way suggested by @matthiasradde?

Hello, thanks for your time.
I was reading that article and made the suggestions indicated there but the problem persists; In addition, I tried in other browsers such as brave, chrome and edge, in which the error is different: “localhost closed the connection unexpectedly.”, just to mention that I do not have any proxy configured.

The need to create it is purely educational, that’s why I want to do it locally.

I don’t have a domain, it’s just that they try to upload the docker to Google, but they started charging me money and nothing worked for me; Therefore I gave up on that option.

Precisely, searching for information and guides (on Google) I came to this thread and started following these suggestions.

I am in a project (I am a Computer Technologist student); I have a web application written in PHP, which must consume certain technologies in order to gain experience; I must use Sql, NoSql and memcached databases; I managed to do this in xampp; Now I have to dockerize the solution, but I am faced with the problem of configuring the self-signed certificate. (Hope it’s understandable)

this is my Dockerfile

FROM php:apache
RUN docker-php-ext-install mysqli && docker-php-ext-enable mysqli && docker-php-ext-install pdo_mysql
RUN a2enmod rewrite && a2enmod ssl && a2enmod socache_shmcb
RUN apt-get update && apt-get upgrade -y
EXPOSE 443

and this is my docker-compose

version: '3.9'
services:
  apache:
    build:
      context: .
      dockerfile: Dockerfile
    links:
      - mysql
      - mongo
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./src:/var/www/html
      - ./logs/apache:/usr/local/apache2/logs
      - ./config/cert/dockerC.crt:/etc/ssl/certs/dockerC.crt
      - ./config/cert/dockerK.key:/etc/ssl/private/dockerK.key
      - ./config/cert/default-ssl.conf:/etc/apache2/sites-available/default-ssl.conf
      - ./config/php:/usr/local/etc/php/  # Ruta al archivo php.ini personalizado
    environment:
      - PHP_DISPLAY_ERRORS=1
      - PHP_MEMORY_LIMIT=2048M
      - PHP_MAX_EXECUTION_TIME=300
      - PHP_POST_MAX_SIZE=500M
      - PHP_UPLOAD_MAX_FILESIZE=256M
    depends_on:
      - mysql
      - mongo

  mysql:
    image: mysql:8.2.0
    environment:
      MYSQL_ROOT_PASSWORD: admin
      MYSQL_DATABASE: admin
      MYSQL_USER: uMysql
      MYSQL_PASSWORD: admin
    ports:
      - "3306:3306"
    volumes:
      - ./databases/mysqldata:/var/lib/mysql
      - ./logs/mariadb:/var/log/mysql

  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    links:
      - mysql
    ports:
      - "8080:80"
    environment:
      PMA_HOST: mysql
      MYSQL_ROOT_PASSWORD: admin
    depends_on:
      - mysql

  mongo:
    image: mongo:latest
    environment:
      - MONGO_INITDB_ROOT_USERNAME=root
      - MONGO_INITDB_ROOT_PASSWORD=password
    ports:
      - "27017:27017"
    volumes:
      - ./databases/mongodb/db:/data/db
      - ./databases/mongodb/dev.archive:/Databases/dev.archive
      - ./databases/mongodb/production:/Databases/production
      - ./logs/mongodb:/var/log/mongodb

  mongo-express:
    image: mongo-express
    environment:
      - ME_CONFIG_MONGODB_ADMINUSERNAME=root
      - ME_CONFIG_MONGODB_ADMINPASSWORD=password
      - ME_CONFIG_MONGODB_URL=mongodb://root:password@mongo:27017/?authSource=admin
      - ME_CONFIG_BASICAUTH_USERNAME=mexpress
      - ME_CONFIG_BASICAUTH_PASSWORD=mexpress
    links:
      - mongo
    ports:
      - "8081:8081"

  memcached:
    image: memcached:latest
    ports:
      - "11211:11211"

default-ssl.conf

<VirtualHost *:443>
    ServerAdmin webmaster@localhost
    DocumentRoot /var/www/html
    ErrorLog ${APACHE_LOG_DIR}/error.log
	CustomLog ${APACHE_LOG_DIR}/access.log combined
    SSLEngine on
    SSLCertificateFile /etc/ssl/certs/dockerC.crt
    SSLCertificateKeyFile /etc/ssl/private/dockerK.key
</VirtualHost>

with this linea create the certificates

openssl req -x509 -new -out ./config/cert/dockerC.crt -keyout ./config/cert/dockerK.key -days 365 -newkey rsa:4096 -sha256 -nodes

the file hosts

127.0.0.1 localhost prueba.com
::1     localhot prueba.com ip6-localhost ip6-loopback

Likewise, the alias “prueba.com” does not work either

Hello @warriol
please create the certificate using the actions I wrote in December 2022 (instead of using the ones from October 2021) to create a CA and a certificate with SAN (subject alternate name) as (if I remember correctly) at the time of writing browsers start to refuse certificates using only the CN instead of SAN.
So please try it again to create a certificate with mentioned actions from december 2022 and keep following in mind:

  • the CN of the CA and the certificates have to be different
  • the certificate’s CN has to be repeated within the extension-file as one of the DNS.x-entries as browsers rely only on the SAN-values

Hello
I followed step by step what you have written dated December 2022.
I was able to create all the files and upon verification it gave OK.
I didn’t know which particular directory I should upload each file to, so from docker-compose I uploaded them like this:
- ./config/ssl/webserver-pub.pem:/etc/ssl/webserver-pub.pem
- ./config/ssl/webserver-key.pem:/etc/ssl/webserver-key.pem
- ./config/ssl/ca-root.pem:/etc/ssl/ca-root.pem

To the virtualhost I added these lines:
SSL certificate file /etc/ssl/webserver-pub.pem
SSLCertificateKeyFile /etc/ssl/webserver-key.pem
SSLCA certificate file /etc/ssl/ca-root.pem

Now the error is different:
Error code: SSL_ERROR_RX_RECORD_TOO_LONG

These configurations exceed the project and perhaps I am getting too complicated, I will leave it to learn it later, sorry for the inconvenience and thank you.

This page shows up at the top in google search for docker ssl, but does not mention the easiest solution. For those who might tread this path in future,

# Enable SSL
RUN set -eux; \
    apt-get update; \
    apt-get install ssl-cert; \
    a2enmod ssl; \
    a2ensite default-ssl

And it works just like that, without all the roundabouts/ gimmics/ cert creation/ etc. Spend a lot of time figuring this out, before coming across this.

Source: php - Enable Apache SSL in Docker for local development - Stack Overflow