Generating a self-signed certificate with openssl is surprisingly straightforward, but the real magic is understanding how it isn’t trusted by anyone and why you’d use it anyway.

Let’s see it in action. We’ll create a certificate for a fictional internal service, internal-api.local.

# Create a private key
openssl genrsa -out internal-api.key 2048

# Create a Certificate Signing Request (CSR)
openssl req -new -key internal-api.key -out internal-api.csr \
  -subj "/C=US/ST=California/L=San Francisco/O=MyCompany/CN=internal-api.local"

# Sign the CSR to create the self-signed certificate
openssl x509 -req -days 365 -in internal-api.csr -signkey internal-api.key -out internal-api.crt

At this point, you have internal-api.key (your private key) and internal-api.crt (your public certificate). If you were to try and use internal-api.crt to secure a web server, your browser would scream bloody murder about an untrusted connection.

So, what’s the point?

Self-signed certificates are primarily for development and testing environments, or for internal-only services where you control all the endpoints and can distribute the certificate manually. They allow you to encrypt traffic (TLS/SSL) without the cost and hassle of getting a certificate from a public Certificate Authority (CA).

Here’s the breakdown of what those commands do:

  1. openssl genrsa -out internal-api.key 2048: This generates your private key. It’s a 2048-bit RSA key, which is a good balance between security and performance for most use cases. This key is secret and must be protected. It’s what actually performs the cryptographic operations.
  2. openssl req -new -key internal-api.key -out internal-api.csr -subj "/C=US/ST=California/L=San Francisco/O=MyCompany/CN=internal-api.local": This creates a Certificate Signing Request (CSR). A CSR contains information about the entity that wants a certificate (your company, your domain name, etc.) and is signed by your private key. The -subj flag directly embeds the subject information. The CN (Common Name) is crucial; it’s the hostname the certificate will be valid for (e.g., internal-api.local).
  3. openssl x509 -req -days 365 -in internal-api.csr -signkey internal-api.key -out internal-api.crt: This is the core step for a self-signed certificate. You’re taking the CSR (internal-api.csr), using your own private key (internal-api.key) to sign it, and issuing a certificate (internal-api.crt) that’s valid for 365 days. Since you signed it with your own key, it’s inherently trusted by you, but not by anyone else.

The entire point of a certificate is to prove an identity. A certificate from a public CA is trusted because your operating system and browser have a pre-installed list of "root" CAs that they do trust. When a CA signs your certificate, it’s essentially vouching for your identity. When you self-sign, you’re vouching for yourself, and nobody else has a reason to believe you.

The most surprising thing is how you actually deploy this. For a web server, you’d configure it to use both the private key and the certificate. For example, in Nginx, you’d have something like:

server {
    listen 443 ssl;
    server_name internal-api.local;

    ssl_certificate /etc/ssl/certs/internal-api.crt;
    ssl_certificate_key /etc/ssl/private/internal-api.key;

    # ... other configuration
}

Then, on each client that needs to connect to internal-api.local, you would need to manually import internal-api.crt into their trusted certificate store. This is the fundamental limitation: you have to distribute trust yourself.

If you’re creating a certificate for a service that needs to be accessed by multiple internal machines, you might also want to include Subject Alternative Names (SANs) for other hostnames or IP addresses the certificate should be valid for. This is done by creating an openssl.cnf file and referencing it during the openssl x509 command.

The next step you’ll likely encounter is needing to generate a certificate that is trusted by others, leading you down the path of using Let’s Encrypt or purchasing a certificate from a commercial CA.

Want structured learning?

Take the full Tls-ssl course →