36

I'm trying to add SSL certs (generated with LetsEncrypt) to my nginx. The nginx is built from a docker-compose file where I create a volume from my host to the container so the containers can access the certs and private key.

volumes:
  - /etc/nginx/certs/:/etc/nginx/certs/

When the nginx container starts and fails with the following error

[emerg] 1#1: BIO_new_file("/etc/nginx/certs/fullchain.pem") failed 
(SSL: error:02001002:system library:fopen:No such file or 
directory:fopen('/etc/nginx/certs/fullchain.pem','r') 
error:2006D080:BIO routines:BIO_new_file:no such file)

My nginx config file looks like this:

server {
    listen 80;
    server_name server_blah www.server_blah;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl;
    server_name server_blah;
    ssl_certificate      /etc/nginx/certs/fullchain.pem;
    ssl_certificate_key  /etc/nginx/certs/privkey.pem;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers         HIGH:!aNULL:!MD5;
}

What am I missing/doing incorrectly?

2
  • 1
    What is the output of ls /etc/nginx/certs on your server? Commented Jul 18, 2018 at 11:16
  • Here's a screenshot of it when I ls i.imgur.com/aTuBatt.png Commented Jul 18, 2018 at 11:24

3 Answers 3

37

Finally cracked this and was able to successfully repeat the process on my dev and production site to get SSL certs working!

Sorry for the length of the post!

In my setup I have docker docker-compose setup on an ubuntu 16 machine.

Anyone who's encountering this problem I'll detail the steps I did.

  1. Go to the directory where your code lives

    cd /opt/example_dir/

  2. Make a directory for letsencrypt and it's site.

    sudo mkdir -p /opt/example_dir/letsencrypt/letsencrypt-site

  3. Create barebones docker-compose.yml file from the letsencrypt directory.

    sudo nano /opt/example_dir/letsencrypt/docker-compose.yml

Add the following to it:

version: '2'

services:
    image: nginx:latest
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
      - ./letsencrypt-site:/usr/share/nginx/html
    networks:
      - docker-network

networks:
  docker-network:
    driver: bridge
  • This will pull down the latest nginx version
  • Expose port 80
  • Mount a config file (that i'll create later)
  • Maps the site directory so that we can have a simple test index.html for when

we start the simple nginx container.

  1. Create an nginx.conf file in /opt/example_dir/letsencrypt

    sudo nano /opt/example_dir/letsencrypt/nginx.conf

Put the following into it

server {
  listen 80;
  listen [::]:80;
  server_name example_server.com;

  location ~ /.well-known/acme-challenge {
      allow all;
      root /usr/share/nginx/html;
  }

  root /usr/share/nginx/html;
  index index.html;
}
  • This listens for requests on port 80 for the server with name example_server.com
  • Gives the Certbot agent access to ./well-known/acme-challenge
  • Sets the default root and file
  1. Next create an index.html file within /opt/example_dir/letsencrypt/letsencrypt-site

sudo nano /opt/example_dir/letsencrypt/letsencrypt-site/index.html

Add the following to it

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>LetsEncrypt Setup</title>
</head>
<body>
    
    <p>Test file for our http nginx server</p>
</body>
</html>

All parts in place for basic nginx container!


  1. Now we start up the nginx container.
cd /opt/example_dir/letsencrypt
sudo docker-compose up -d

The nginx container is up and running now, visit the url you've defined and you should get the test index.html page back. At this point we're ready to run the certbot command to generate some certs

  1. Run the following to generate certs replacing --email with your email
sudo docker run -it --rm \
    -v /docker-volumes/etc/letsencrypt:/etc/letsencrypt \
    -v /docker-volumes/var/lib/letsencrypt:/var/lib/letsencrypt \
    -v /opt/example_dir/letsencrypt/letsencrypt-site:/data/letsencrypt \
    -v "/docker-volumes/var/log/letsencrypt:/var/log/letsencrypt" \
    certbot/certbot \
    certonly --webroot \
    --email [email protected] --agree-tos --no-eff-email \
    --webroot-path=/data/letsencrypt \
    -d example.com
  • Run docker in interactive mode so you can see the output.
  • When its finished generating certs it will remove itself.
  • It will mount 4 volumes:
    1. The letsencrypt folder where the certs are stored/
    2. A lib folder
    3. Maps our site folder
    4. Maps a logging path
  • It agrees to ToS
  • Specifies the root webpath
  • Specify the server address you want to generate certs for.

If that command ran okay then we have generated certs for this web server. We can now use these in our production site and configure nginx to use the ssl and make use of these certs!

  1. Shut down the nginx container
cd /opt/example_dir/letsencrypt/
sudo docker-compose down

Setup Production nginx container

Directory structure should look like this now. Where you have your code / web app project and then the letsencrypt folder that we created above.

/opt/example_dir
     / -> project_folder
     / -> letsencrypt
  1. Create a folder call dh-param
sudo mkdir -p /opt/example_dir/project_folder/dh-param
  1. Generate a dh key
sudo openssl dhparam -out /opt/example_dir/project_folder/dhparam/dhparam-2048.pem 2048
  1. Update docker-compose.yml and nginx.conf files within /opt/example_dir/project_folder

The project_folder is where my source code lives so I create a production config file here for nginx and update the docker-compose.yml to mount my nginx config, dh-pharam exchange key as well as the certs themselves we created earlier.

nginx service in the docker-compose

nginx:
    image: nginx:1.11.3
    restart: always
    ports:
      - "80:80"
      - "443:443"
      - "8000:8000"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
      - ./dh-param/dhparam-2048.pem:/etc/ssl/certs/dhparam-2048.pem
      - /docker-volumes/etc/letsencrypt/live/exampleserver.com/fullchain.pem:/etc/letsencrypt/live/exampleserver.com/fullchain.pem
      - /docker-volumes/etc/letsencrypt/live/exampleserver.com/privkey.pem:/etc/letsencrypt/live/exampleserver.com/privkey.pem
    networks:
      - docker-network

    volumes_from:
      - flask
    depends_on:
      - flask
      - falcon
    links:
      - datastore

nginx.conf within project_folder

error_log /var/log/nginx/error.log warn;

server {
    listen 80;
    listen [::]:80;

    server_name exampleserver.com

    location / {
        rewrite ^ https://$host$request_uri? permanent;
    }

    #for certbot challenges (renewal process)
    location ~ /.well-known/acme-challenge {
        allow all;
        root /data/letsencrypt;
    }
}

#https://exampleserver.com
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name exampleserver.com;

    server_tokens off;

    ssl_certificate /etc/letsencrypt/live/exampleserver.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/exampleserver.com/privkey.pem;

    ssl_buffer_size 8k;

    ssl_dhparam /etc/ssl/certs/dhparam-2048.pem;

    ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
    ssl_prefer_server_ciphers on;

    ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;

    ssl_ecdh_curve secp384r1;
    ssl_session_tickets off;

    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 8.8.8.8;


    # Define the specified charset to the “Content-Type” response header field
    charset utf-8;
}

At this point everything is setup! (finally)

  1. Spin up the docker container.
cd /opt/example_dir/project_folder
sudo docker-compose up -d

# Check the docker log with:
sudo docker logs -f -t  

I know it's a lot of steps but this is what I have done, it's worked for me and hope it helps someone else.

Sign up to request clarification or add additional context in comments.

6 Comments

It works all good before 9th section, after that it throws me an error: Error getting validation data
How do you renew the certificates? Did you automate that?
@AntonyHatchkins I used certbot to auto-renew the certificates. You have to add a section within the nginx config file. Then I set up a cronjob to trigger a cert renewal check. If the certs were within 30 days of expiring they got updated
Afaik neither crond nor systemd scheduler don't work in docker containers by default. Most of the guides that I've run into automate the renewal through sleeping in bash. Do you run the cronjob on the host or in the container?
@AntonyHatchkins This is quite old, so I had a look back at the code! Turns out I created a python script called run_cron.py It used the subprocess module and the call method. call(["crond", "-f", "-L 15"]). I had a docker-compose file for all the services. So for each service I needed cronjobs. I used the command option. So like this command: python3 /path/to/app/run_cron.py Thinking back I remember hitting the same issue with crons and docker containers but this setup works. There's probably a better way but it suited at the time.
|
2

I've got the exact same problem right now. Same error codes.

I tried different things on my .pem files:

  • Changing permissions (chmod) to:
    • 777
    • 755
    • 600
  • Changing the owner (chown) to:
    • nginx (user defined in nginx.conf)
    • root
  • Changing the location to:
    • /etc/ssl/certs
    • /etc/nginx/ssl
    • /etc/letsencrypt/live
  • Changing docker mounting to:
    • ro (readonly)
    • rw (readwrite)

Sadly, none of those solutions worked for me.

It really looks like nginx isn't able to find the files even if I'm able to list (ls) them when I connect to the container terminal.

To give some more details, I'm running Docker on a Synology DS918+ NAS.

I hope this will help to find the solution! I'll be experimenting and trying various things, I'll come back if I manage to get this working!

1 Comment

I resolved it and got it working at last. Wrote a length answer below. Hope it helps you.
2

My solution:

I have mapped /etc/letsencrypt/live/mydomain.com/ to /etc/nginx/certs/ by:

volumes:
  - /etc/letsencrypt/live/mydomain.com/:/etc/nginx/certs/

In this case, it has mapped the soft symbolic link.

lrwxrwxrwx 1 root root   38 Sep 15 00:21 chain.pem -> ../../archive/mydomain.com/chain1.pem
lrwxrwxrwx 1 root root   42 Sep 15 00:21 fullchain.pem -> ../../archive/mydomain.com/fullchain1.pem
lrwxrwxrwx 1 root root   40 Sep 15 00:21 privkey.pem -> ../../archive/mydomain.com/privkey1.pem

Finally, I changed the volume section as the following:

volumes:
  - /etc/letsencrypt/archive/mydomain.com/:/etc/nginx/certs/

1 Comment

Same idea worked for me. Use volumes to make it all easy. Keep file names same as in ngnix config file. Remove excess permissions if it still does not work. I had too many users with access on each file.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.