Self-signed certificates are often treated as a functional equivalent to publicly trusted certificates, but they are fundamentally different in how trust is established and enforced.

Let’s see what happens when a browser encounters a self-signed certificate. Imagine you’re running a small internal web server for your team, and you’ve generated a certificate for it yourself.

GET / HTTP/1.1
Host: internal.dev.local
User-Agent: curl/7.64.1
Accept: */*

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1234
Server: Apache/2.4.41 (Ubuntu)
Date: Mon, 01 Jan 2024 12:00:00 GMT
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Strict-Transport-Security: max-age=31536000; includeSubDomains

<!DOCTYPE html>
<html>
<head>
    <title>Internal Dashboard</title>
</head>
<body>
    <h1>Welcome to the Team Dashboard!</h1>
    <p>This is an internal service.</p>
</body>
</html>

When your browser connects to https://internal.dev.local, it receives this self-signed certificate. The browser’s first step is to check if the certificate’s issuer is trusted. Unlike certificates issued by Certificate Authorities (CAs) like Let’s Encrypt or DigiCert, which are pre-installed in the browser’s trust store, the issuer of a self-signed certificate is, well, itself. The browser’s security mechanism, designed to protect users from impersonation, sees this unfamiliar issuer and flags the connection as insecure. This is why you get a scary warning page: "Your connection is not private," "Potential security risk ahead," or similar. The browser cannot cryptographically verify that the server you’re talking to is actually internal.dev.local and not an imposter.

The core problem self-signed certificates solve is enabling TLS/SSL encryption for a server without requiring an external authority. This is useful for development, testing, or internal-only services where the risk of public impersonation is low or managed by other means. However, their weakness is precisely this lack of external validation. Anyone can create a self-signed certificate for any domain name. Without a trusted third party vouching for the certificate’s authenticity, the browser has no way to know if the server presenting the certificate is legitimate or a malicious actor trying to intercept your traffic.

The system works by having the server present its certificate during the TLS handshake. The client (your browser) then inspects this certificate. It checks:

  1. The issuer: Is it someone the client trusts?
  2. The validity period: Is the certificate still current?
  3. The domain name: Does the certificate’s "Subject Alternative Name" (SAN) or "Common Name" (CN) match the domain the client is trying to reach?
  4. The signature: Is the certificate’s signature valid, proving it was issued by the claimed issuer?

For a self-signed certificate, the issuer is the same as the subject. The client’s trust store doesn’t contain an entry for "Self-Signed-Issuer," so the chain of trust breaks immediately. The browser, by default, refuses to proceed unless the user explicitly overrides the warning, which is a dangerous practice for any public-facing site.

For internal services, the practical alternatives to self-signed certificates generally fall into two categories: using a private Certificate Authority (CA) or obtaining certificates from a publicly trusted CA.

If you manage an organization with many internal services, setting up your own private CA is a robust solution. Tools like openssl can be used to generate a root CA certificate and then use that root to sign certificates for your internal servers. You then distribute this root CA certificate to all client machines within your network and instruct their operating systems or browsers to trust it. This establishes a chain of trust entirely within your organization. For example, to create a root CA and then a server certificate:

  1. Create Root CA:
    # Generate a private key for the CA
    openssl genrsa -aes256 -out ca.key 4096
    # Create the root certificate
    openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt -subj "/CN=MyInternalCA"
    
  2. Create Server Certificate Signing Request (CSR):
    # Generate a private key for the server
    openssl genrsa -out server.key 2048
    # Create a CSR for the server
    openssl req -new -key server.key -out server.csr -subj "/CN=internal.dev.local"
    
  3. Sign the Server Certificate with the Root CA:
    # Sign the CSR with the CA's key
    openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 730 -sha256 -extfile <(printf "subjectAltName=DNS:internal.dev.local")
    
    The server.crt is then installed on your web server. The ca.crt needs to be imported into the trust stores of all clients that will access this server. This process ensures that the client trusts the CA, and by extension, trusts any certificate signed by that CA for internal use.

For services that are only intended for internal use but might still benefit from public trust (e.g., for ease of access without manual trust store manipulation), you can obtain certificates from a publicly trusted CA. Let’s Encrypt offers free, automated certificates. While typically used for public websites, it can be configured for internal hostnames if your internal network can resolve public DNS records for those hostnames (which is often not the case). Alternatively, you can purchase certificates from commercial CAs, but this is usually overkill and costly for purely internal services.

The "safety" of self-signed certificates is entirely dependent on the environment and the user’s awareness. In a development sandbox where you control every aspect and no sensitive data is exchanged, they might be "safe enough." In any scenario involving multiple users, untrusted networks, or sensitive data, they are a significant security risk because they bypass the fundamental mechanism of trust verification in TLS. The browser warning is not an inconvenience; it’s a critical alert that the identity of the server cannot be verified.

The next step after securing your internal services with a private CA is to implement certificate revocation for compromised or expired internal certificates.

Want structured learning?

Take the full Tls-ssl course →