🚨 This page covers approaches for self-signed certs; these are useful for local dev work, testing, and special use-cases. For actual production websites, you should not be using self-signed certificates - if you want a free alternative to paid SSL certs, I cannot recommend Let's Encrypt enough!
- Self-Signed SSL Certificates: Basic Requirements
- Using OpenSSL to Generate Self-Signed Certs
- OpenSSL - Temporary CSR - Prompt Based
- OpenSSL - Generating Self-Signed Cert Without Prompts
- OpenSSL - Saving a Generated CSR
- OpenSSL - Using an Existing Saved CSR File to Generate a New Certificate
- OpenSSL - Verifying and Viewing SSL Info
- Generating Certificates: Scripts and Generator Tools
- Local Hosting - Trusting a Self-Signed Cert
- Other Resources
The basic requirements for local SSL hosting, and setting up self-signed SSL in general, are:
- A private key (
- A CSR (Certificate Signing Request)
- A public certificate (usually
The CSR is kind of intermediate step between the key and the cert (the CSR is signed by the key to become the cert, so in many cases it is temporarily created via a prompt). You can think of the cert as being derived from the key, via the CSR.
💡 SSL cert pairs are often referred to with the
X.509identifier; this is essentially the standard that is used for TLS/SSL, but it is used for other purposes as well.
However, how you generate these is a little more complicated than how you might normally generate key-pairs with OpenSSL, since it is best practice to provide more configuration inputs to create the certificate, such as DNS entries that can use the certificate. There are also different approaches, based on how you want to use the cert you are generating.
💡 If you are on Windows, there is a handy built-in Powershell Cmdlet that simplifies generating a local dev certificate a lot! - the
💡 Also, don't forget that if you want to use a custom local domain (like using
localhost) you need to edit your local HOSTS file to add an entry resolving localhost (or
127.0.0.1) to that custom local domain.
Before you get started with any of the above approaches, you are going to need the actual OpenSSL binary. There are pretty good chances you might already have it installed (especially if you do a fair amount of dev work) - try running
where openssl (Windows), or
which openssl (Unix).
You can check the version of local binary with
Now, on to how to actually use OpenSSL to generate your own cert! 👇
With this method, you don't need to create a CSR as a separate step; instead OpenSSL will prompt you (via the CLI) for the minimum information it needs to temporarily create a CSR, then use that temporary CSR with your private key to generate cert.
This method is commonly recommended, because it involves the least steps.
openssl req -newkey rsa:4096 -nodes -keyout domain.key -x509 -sha256 -days 365 -out domain.crt
Explanation of command parts:
req: This is the main command we are passing to OpenSSL - it asks OpenSSL to generate a CSR for us. (Docs).
- The rest of the arguments we provide can be found on the doc for this command
-newkey: Says that we want both a new CSR and a new private key
rsa:4096: algorithm and number of bits to use when creating the private key. Many versions of OpenSSL now use
2048as the default bit size
-nodes: Tells OpenSSL not to encrypt the private key with a password / passphrase. Normally, this is a bad idea, but since this is a throwaway local self-signed cert for development purposes, adding a password adds unnecessary steps to our setup.
-keyout domain.key: Save the generated private key to
domain.keyfile. We can use any filname we want
-x509: Important: This tells OpenSSL that the output of our command should be a Self-Signed Certificate, not the CSR used to generate it.
-sha256: Explicitly specifies the algorithm, for the message digest, used to sign the CSR and generate the final certificate. The default is now
sha256, but it is worth explicitly declaring, since older versions default to insecure
-days 365: How long the cert should be valid for, before it expires.
- If you are going to add a permanent exception to your browser to trust the local cert, you should probably put this to a shorter value to avoid adding too much extra risk
out domain.crt: Tells OpenSSL where to save our generated cert file. You can use any filename.
This is very similar to the above approach, but instead of filling out the CLI Q&A prompt that OpenSSL gives us, we can pass in pre-configured values to use with the CSR. This is useful if we want to be able to share a configuration that other devs can use to generate their own local certs, create a base template config, etc.
You can pass configuration values either entirely through the CLI, as arguments, or by giving OpenSSL the filename of a plain-text file where you have saved the values (in a specific config file format).
📄 Here is a doc page specifically for
# Broken out openssl req \ -newkey rsa:4096 -nodes \ -keyout domain.key \ -x509 \ -sha256 -days 365 \ # Here is where we start adding the config stuff, # ,which would normally be passed via prompt # --- Distinguished Name Section --- -subj "/C=US/ST=WA/L=SEATTLE/O=MyCompany/OU=MyDivision/CN=*.domain.test" \ # --- SAN (subject alternative names) Section --- # Notice that multiple values are comma separated; do not try to add # multiple extension entries for the same extension # This syntax requires >= v1.1.1 -addext "subjectAltName = DNS:*.domain.test, DNS:localhost, DNS:127.0.0.1, DNS:mail.domain.test" \ # ^ If you are on v <= v1.1.0, this is notoriously difficult to pass via # CLI, as it appears extension values must be passed via conf file. # - See: https://security.stackexchange.com/a/91556/248319 # Same as before -out domain.crt \ # Same as above, but all together: openssl req -newkey rsa:4096 -nodes -keyout domain.key -x509 -sha256 -days 365 -subj "/C=US/ST=WA/L=SEATTLE/O=MyCompany/OU=MyDivision/CN=*.domain.test" -addext "subjectAltName = DNS:*.domain.test, DNS:localhost, DNS:127.0.0.1, DNS:mail.domain.test" -out domain.crt
If the above command failed for you (perhaps with
req: Unknown digest addext), check your version of OpenSSL (with
openssl version). If it is below
v1.1.1, you either need to use a config file to pass in extension values (see below), or use some tricky bash sub-shell stuff.
One really important thing to note about the config file syntax is that often values are passed by section lookup, rather than directly. So, for example, if we wanted to pass custom alternative names (DNS entries), we would pass a pointer to the section of the config where we have entered those values.
In this scenario, we can use any header name we want, but assuming we went with
alternate_names, we would need a key-pair under our extensions with
subjectAltName = @alternate_names, and then a section with the header
[alternate_names ], under which our DNS entries are stored (e.g.
DNS.1 = localhost).
📄 For config keys that go with
req, see the bottom of the
📄 Here is a doc page specifically for
x509(cert) config sections
To actually load the config values, use the
-config argument, and pass your filename. For example, here is the typical command to generate a private key and self-signed cert, with input from a config.
openssl req -config myconfig.conf -newkey rsa -x509 -days 365 -out domain.crt
Example Config File, with comments:
# Key: # - <SK> = Key to a config subsection # Main entry point # since our command on the CLI is `req`, OpenSSL is going to look for a matching entry-point # This lets you store multiple command configs together, in a single file [req] # algorithm and number of bits to use when creating the private key # rsa:2048 default_bits = 2048 # Same as `-nodes` argument, to prevent encryption of private key (passphrase) encrypt_key = no # Explicitly tells OpenSSL which message digest algorithm to use # Good practice to specify, since older versions might default to MD5 (insecure) default_md = sha256 # If you don't use `-keyout` in the CLI, this determines the private key filename default_keyfile = domain.key # <SK> These are values that are used to *distinguish* the certificate, such as the country and organization # These values are normally collected via Q&A prompt in the CLI if config file is not used distinguished_name = req_distinguished_name # Ensures that distinguished_name values will be pulled from this file, as # opposed to prompting the user in the CLI prompt = no # <SK> Used for extensions to the self-signed cert OpenSSL is going to generate for us x509_extensions = x509_extensions [req_distinguished_name] # - These are all values that are used to *distinguish* the certificate, such as # the country and organization # - Many of these have shorter keys that should be used for non-prompt values, # and long keys that should have a prompt string to display to the user, and # optionally a default value if the prompt is skipped (see below note) # - For long keys, if you use fieldName with `_default` at the end, the value # will be used if prompt!==true, or if the user skips the prompt in the CLI # Long = countryName C = US # Long = stateOrProvinceName ST = WA # Long = localityName L = Seattle # Long = organizationName O = MyCompany # Long = organizationalUnitName OU = MyDivision # Long = commonName # Pay extra attention to common name - You can only define one, and it is the # value that is displayed to the user. Should NOT include protocol, but can # be in format of domain.tld, www.domain.tld, or even wildcard, to share a # common cert across multiple subdomains - `*.domain.tld`. # Also, any value that you use here !*** MUST ***! be ALSO included in the SAN # (subject alternative name) section (subjectAltName), if you choose to # include that section. See: https://stackoverflow.com/a/25971071/11447682 CN = *.domain.test [x509_extensions] # <SK> Used for (generically) custom field-value pairs that should be associated # with the cert, such as extra DNS names, IP addresses, and emails subjectAltName = @alternate_names [alternate_names] # Extra domain names to associate with our cert # - These can be a mix of wildcard, IP address, subdomain, etc. DNS.1 = *.domain.test DNS.2 = localhost DNS.3 = 127.0.0.1 DNS.4 = mail.domain.test # Etc. # See: # - https://www.openssl.org/docs/man1.1.1/man5/x509v3_config.html#Subject-Alternative-Name # - https://en.wikipedia.org/wiki/Subject_Alternative_Name
Above Example Config, Without Comments
[req] default_bits = 2048 encrypt_key = no default_md = sha256 default_keyfile = domain.key distinguished_name = req_distinguished_name prompt = no x509_extensions = x509_extensions [req_distinguished_name] C = US ST = WA L = Seattle O = MyCompany OU = MyDivision CN = *.domain.test [x509_extensions] subjectAltName = @alternate_names [alternate_names] DNS.1 = *.domain.test DNS.2 = localhost DNS.3 = 127.0.0.1 DNS.4 = mail.domain.test
If you want to save the generated CSR, instead of having OpenSSL just temporarily use and throw away the one it uses to create a cert, you just need to change a few arguments.
-x509(this targets Cert as output, not CSR, so don't want it)
-out domain.csr(or whatever filename you prefer)
You can export the CSR while:
- also generating a new private key
- or with an existing private key
Since the Cert is created by signing CSR values with your key, you can also reverse the process, and actually generate a CSR from an existing public certificate, assuming you have the private key:
openssl x509 -in domain.crt -signkey domain.key -x509toreq -out domain.csr
If you already have a saved CSR and private key, you skip providing all the information needed to generate the CSR:
openssl req -text -noout -verify -in domain.csr
openssl x509 -text -noout -in domain.crt
🚨 Be very careful about tools (and try to avoid them) that work by installing a global / root level CA / certificate; this opens a large security hole in your dev environment. See this post for an explanation.
📄 If you find it easier to read code, rather than bash commands, you might find it helpful to look at how mockttp handles TLS.
These are tools that can make creating local certs a little less painful than a bunch of hard to remember commands. Most of these are just wrappers around OpenSSL.
- mkcert (🚨 = Uses root CA)
- Windows Built-in Tools:
Once you have your certificate generated, that's not actually the end of the work necessary to use SSL locally. Because browsers (generally) don't trust self-signed certificates, you need to an exception to allow your specific certificate to be trusted.
As a best practice, trusting a local self-signed certificate should be done temporary, and on a per-site basis (not with a root / global / shared CA).
Temporary certificate trusting:
- Get the fingerprint of the cert
- Launch Chrome, temporarily, with the fingerprint of the cert to trust:
chrome --ignore-certificate-errors-spki-list=$(cat fingerprints.txt)
- Also, you can pass your domain as the last argument to have it automatically open to that page
💡 For Chrome, you probably also want / need to have it force opened in a new instance, rather than your normal browser. You can do this with either
chrome --user-data-dir=/tmp(*nix), or, on Windows,
- DigitalOcean: "OpenSSL Essentials"
- StackOverflow: "How to create a self-signed certificate with OpenSSL