How to create locally trusted SSL certificates in local development environment on Linux with mkcert
A Certificate Authority (CA) is an entity that issues digital certificates. Our goal is to become a certificate authority (CA) like Let’s Encrypt, Verisign etc.. Issue yourself certificates for local development domains!
So, how do you remove the “your connection is not private” error in the browser when you visit a localhost domain?
mkcert is a simple zero-config tool written by Filippo Valsorda in Go for making locally trusted development certificates with any names you’d like without any configuration. This will help you since it is impossible to get certificates from trusted Certificate Authorities for local names that don’t have a valid DNS record. So let’s dive in to install and use mkcert.
We will show you how to become a Certificate Authority in few steps.
How to Install mkcert on Ubuntu / Debian
To install mkcert on any Ubuntu or Debian system, first, install certutil dependencies:
sudo apt-get update sudo apt install wget libnss3-tools
Once this has been installed, download mkcert binary package from Github. Check mkcert releases page for the latest version. The latest release is.v1.3.0
export VER="v1.3.0" wget -O mkcert https://github.com/FiloSottile/mkcert/releases/download/${VER}/mkcert-${VER}-linux-amd64
Once the file has been downloaded, make the file executable and place the binary under /usr/local/bin
chmod +x mkcert sudo mv mkcert /usr/local/bin
How to Install mkcert on CentOS
Installation of mkcert on CentOS is similar to Ubuntu / Debian installation. You only need to install nss-tools tools first.
sudo yum install nss-tools
Once installed, download the binary package like for Ubuntu installation.
export VER="v1.3.0" wget -O mkcert https://github.com/FiloSottile/mkcert/releases/download/${VER}/mkcert-${VER}-linux-amd64
Once the file has been downloaded, make the file executable and place the binary under /usr/local/bin
chmod +x mkcert sudo mv mkcert /usr/local/bin
How to use mkcert to generate locally trusted SSL certificates
mkcert has support for the following root stores:
- macOS system store
- Windows system store
- Linux variants that provide either
- update-ca-trust (Fedora, RHEL, CentOS)
- update-ca-certificates (Ubuntu, Debian)
- Firefox (macOS and Linux only)
Chrome and Chromium
Java (when JAVA_HOME is set)
To get the help page for mkcert, pass the option–help.
Usage of mkcert: $ mkcert -install Install the local CA in the system trust store. $ mkcert example.org Generate "example.org.pem" and "example.org-key.pem". $ mkcert example.com myapp.dev localhost 127.0.0.1 ::1 Generate "example.com+4.pem" and "example.com+4-key.pem". $ mkcert "*.example.it" Generate "_wildcard.example.it.pem" and "_wildcard.example.it-key.pem". $ mkcert -uninstall Uninstall the local CA (but do not delete it). Advanced options: -cert-file FILE, -key-file FILE, -p12-file FILE Customize the output paths. -client Generate a certificate for client authentication. -ecdsa Generate a certificate with an ECDSA key. -pkcs12 Generate a ".p12" PKCS #12 file, also know as a ".pfx" file, containing certificate and key for legacy applications. -csr CSR Generate a certificate based on the supplied CSR. Conflicts with all other flags and arguments except -install and -cert-file. -CAROOT Print the CA certificate and key storage location. $CAROOT (environment variable) Set the CA certificate and key storage location. (This allows maintaining multiple local CAs in parallel.) $TRUST_STORES (environment variable) A comma-separated list of trust stores to install the local root CA into. Options are: "system", "java" and "nss" (includes Firefox). Autodetected by default.
You can get your CA root directory using:
mkcert -CAROOT /home/vasilij/.local/share/mkcert
You need to start by installing the local CA in your system trust store.
mkcert -install Using the local CA at "/home/vasilij/.local/share/mkcert"
Once done, you can start generating SSL certificates for your domains. As an example, I’ll generate a new certificate valid for the following names:
- "symfony.local" - "*.symfony.local" - "localhost" - "127.0.0.1" - "::1"
The output will be like below:
mkcert symfony.local '*.symfony.local' localhost 127.0.0.1 ::1 Using the local CA at "/home/vasilij/.local/share/mkcert" ✨ Created a new certificate valid for the following names 📜 - "symfony.local" - "*.symfony.local" - "localhost" - "127.0.0.1" - "::1" Reminder: X.509 wildcards only go one level deep, so this won't match a.b.symfony.local ℹ️ The certificate is at "./symfony.local+5.pem" and the key at "./symfony.local+5-key.pem" ✅
You should be able to view the contents of the certificate:
cat ./symfony.local+5.pem -----BEGIN CERTIFICATE----- MIIEVDCCArygAwIBAgIRAJhl6Nd/HzuUhW+KbbzDAo4wDQYJKoZIhvcNAQELBQAw -----END CERTIFICATE-----
The private key is:
cat ./symfony.local+5-key.pem -----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDWSWAZ7nmmfISs -----END PRIVATE KEY-----
Testing mkcert certificate
Let’s now test mkcert certificate using a simple nginx configuration file. Forgive me this example is done on Ubuntu 19.04 system.
sudo apt-get install nginx
Create a simple web page
cat /etc/nginx/sites-enabled/project.conf server { listen 80 default_server; server_name __PROJECT_DOMAIN__; root /var/www/project/__DOCUMENT_ROOT__; location ^~ /.well-known { allow all; root /data/letsencrypt/; } location / { return 301 https://$host$request_uri; } } server { listen 443 ssl http2 default_server; ssl_certificate /etc/nginx/ssl/symfony.local.crt; ssl_certificate_key /etc/nginx/ssl/symfony.local.key; server_name __PROJECT_DOMAIN__; root /var/www/project/__DOCUMENT_ROOT__; access_log /var/log/nginx/project_access.log; error_log /var/log/nginx/project_error.log; # strip app.php/ prefix if it is present rewrite ^/app\.php/?(.*)$ /$1 permanent; location / { index app.php; try_files $uri @rewriteapp; } location @rewriteapp { rewrite ^(.*)$ /app.php/$1 last; } # expire location ~* \.(?:ico|css|js|gif|jpe?g|png|svg|woff|woff2|eot|ttf)$ { try_files $uri /website.php/$1?$query_string; access_log off; expires 30d; add_header Pragma public; add_header Cache-Control "public"; } # pass the PHP script to FastCGI server from upstream phpfcgi location ~ ^/(app|app_dev|config|_intellij_phpdebug_validator)\.php(/|$) { fastcgi_pass php-upstream; fastcgi_split_path_info ^(.+\.php)(/.*)$; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param SYMFONY_ENV dev; fastcgi_param HTTPS off; } }
Make sure your /etc/hosts file has a record for used domains.
127.0.0.1 symfony.local