<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dima Stopel</title>
    <description>The latest articles on DEV Community by Dima Stopel (@dimastopel).</description>
    <link>https://dev.to/dimastopel</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3915933%2Ffd352b83-3b98-4212-bd22-a1acd835c215.png</url>
      <title>DEV Community: Dima Stopel</title>
      <link>https://dev.to/dimastopel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dimastopel"/>
    <language>en</language>
    <item>
      <title>Why Subject Alternative Names (SANs) Matter for Modern Browsers</title>
      <dc:creator>Dima Stopel</dc:creator>
      <pubDate>Wed, 06 May 2026 12:38:15 +0000</pubDate>
      <link>https://dev.to/dimastopel/why-subject-alternative-names-sans-matter-for-modern-browsers-30p0</link>
      <guid>https://dev.to/dimastopel/why-subject-alternative-names-sans-matter-for-modern-browsers-30p0</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://cert-depot.com/guides/why-sans-matter" rel="noopener noreferrer"&gt;cert-depot.com&lt;/a&gt;. Free, open-source self-signed certificate generator — no signup, keys never stored.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Why Subject Alternative Names (SANs) Matter for Modern Browsers
&lt;/h1&gt;

&lt;p&gt;The single most common reason self-signed certs stop working in browsers — and the fix.&lt;/p&gt;

&lt;p&gt;For two decades, SSL/TLS certificates identified themselves via the &lt;strong&gt;Common Name&lt;/strong&gt; (CN) field in the Subject Distinguished Name. Today, browsers ignore it completely. If your certificate doesn't have a Subject Alternative Name (SAN) matching the hostname, every modern browser will reject it — even if the CN is exactly right.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;When you visit &lt;code&gt;https://example.com&lt;/code&gt;, the browser checks the server's certificate for a SAN entry containing &lt;code&gt;example.com&lt;/code&gt;. If no SAN matches, the connection is rejected with an error like &lt;code&gt;NET::ERR_CERT_COMMON_NAME_INVALID&lt;/code&gt;. The Common Name is not consulted.&lt;/p&gt;

&lt;h2&gt;
  
  
  When did this change?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;2000:&lt;/strong&gt; RFC 2818 says "if a subjectAltName extension of type dNSName is present, that MUST be used as the identity. Otherwise, the (most specific) Common Name field in the Subject field of the certificate MUST be used."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2012:&lt;/strong&gt; CA/Browser Forum Baseline Requirements mandate SANs for CA-issued certs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2017 (Chrome 58):&lt;/strong&gt; Chrome &lt;a href="https://chromestatus.com/feature/4981025180483584" rel="noopener noreferrer"&gt;removes the CN fallback&lt;/a&gt;. Firefox follows shortly after. Safari had never accepted CN-only certs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Today:&lt;/strong&gt; Every major browser ignores CN.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What a SAN looks like
&lt;/h2&gt;

&lt;p&gt;The SAN is an X.509 extension. In a certificate, it might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;X509v3 Subject Alternative Name:
    DNS:example.com
    DNS:www.example.com
    DNS:*.dev.example.com
    IP:127.0.0.1
    IP:192.168.1.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste any certificate into our &lt;a href="https://cert-depot.com/tools/pem-decoder" rel="noopener noreferrer"&gt;PEM decoder&lt;/a&gt; to see its SAN entries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating a cert with SANs
&lt;/h2&gt;

&lt;p&gt;With &lt;a href="https://cert-depot.com/" rel="noopener noreferrer"&gt;cert-depot.com&lt;/a&gt;: just list each SAN on its own line in the SANs field. The CN is automatically included as a SAN; you only need to list additional names (other domains, wildcards, IPs).&lt;/p&gt;

&lt;p&gt;With OpenSSL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl req &lt;span class="nt"&gt;-x509&lt;/span&gt; &lt;span class="nt"&gt;-newkey&lt;/span&gt; rsa:2048 &lt;span class="nt"&gt;-nodes&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-keyout&lt;/span&gt; key.pem &lt;span class="nt"&gt;-out&lt;/span&gt; cert.pem &lt;span class="nt"&gt;-days&lt;/span&gt; 365 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-subj&lt;/span&gt; &lt;span class="s2"&gt;"/CN=example.com"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-addext&lt;/span&gt; &lt;span class="s2"&gt;"subjectAltName=DNS:example.com,DNS:*.example.com,IP:127.0.0.1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-addext&lt;/code&gt; flag was added in OpenSSL 1.1.1 (2018). On older versions, you need a config file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Types of SAN you can include
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;dNSName&lt;/strong&gt; — a domain like &lt;code&gt;example.com&lt;/code&gt; or wildcard &lt;code&gt;*.example.com&lt;/code&gt;. Wildcards match exactly one label.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;iPAddress&lt;/strong&gt; — a literal IPv4 or IPv6 address. Useful for direct-to-IP testing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;uniformResourceIdentifier&lt;/strong&gt; — a URI. Used for OCSP responder URLs, not hostname matching.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;rfc822Name&lt;/strong&gt; — an email address. Used for S/MIME, not TLS.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wildcard rules
&lt;/h2&gt;

&lt;p&gt;A wildcard like &lt;code&gt;*.example.com&lt;/code&gt; matches &lt;code&gt;foo.example.com&lt;/code&gt; but &lt;strong&gt;not&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;example.com&lt;/code&gt; itself (the apex)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;foo.bar.example.com&lt;/code&gt; (deeper subdomains)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Practical implication: include both &lt;code&gt;example.com&lt;/code&gt; and &lt;code&gt;*.example.com&lt;/code&gt; as separate SAN entries to cover both cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can the CN still be set?
&lt;/h2&gt;

&lt;p&gt;Yes — and it's often set to the primary domain for readability in cert listings. But browsers ignore it. Set it to whatever the primary name is, and make sure that same name is in the SAN list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/self-signed-cert-nginx" rel="noopener noreferrer"&gt;Configure your cert on Nginx&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/trust-self-signed-cert-chrome" rel="noopener noreferrer"&gt;Trust your cert in Chrome&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ssl</category>
      <category>tls</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Trust a Self-Signed Certificate on Windows</title>
      <dc:creator>Dima Stopel</dc:creator>
      <pubDate>Wed, 06 May 2026 12:31:39 +0000</pubDate>
      <link>https://dev.to/dimastopel/how-to-trust-a-self-signed-certificate-on-windows-387p</link>
      <guid>https://dev.to/dimastopel/how-to-trust-a-self-signed-certificate-on-windows-387p</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://cert-depot.com/guides/trust-self-signed-cert-windows" rel="noopener noreferrer"&gt;cert-depot.com&lt;/a&gt;. Free, open-source self-signed certificate generator — no signup, keys never stored.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  How to Trust a Self-Signed Certificate on Windows
&lt;/h1&gt;

&lt;p&gt;Add your certificate to the Windows Trusted Root store — the GUI and PowerShell ways.&lt;/p&gt;

&lt;p&gt;Windows maintains certificate stores for both the current user and the local machine. For a certificate to be trusted system-wide, install it into &lt;strong&gt;Local Machine › Trusted Root Certification Authorities&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 1: Double-click (easiest)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Rename your certificate to &lt;code&gt;.crt&lt;/code&gt; if it's &lt;code&gt;.pem&lt;/code&gt; (Windows recognizes both, but &lt;code&gt;.crt&lt;/code&gt; triggers the install dialog on double-click).&lt;/li&gt;
&lt;li&gt;Double-click the file. The Certificate dialog opens.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Install Certificate…&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Local Machine&lt;/strong&gt; (requires admin confirmation). Click Next.&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Place all certificates in the following store&lt;/strong&gt;, then &lt;strong&gt;Browse&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Trusted Root Certification Authorities&lt;/strong&gt;, click OK, then Next, then Finish.&lt;/li&gt;
&lt;li&gt;Confirm the security warning dialog.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Method 2: PowerShell
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Import-Certificate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-FilePath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\path\to\certificate.crt"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;-CertStoreLocation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Cert:\LocalMachine\Root&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To install for the current user only (no admin required):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Import-Certificate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-FilePath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\path\to\certificate.crt"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;-CertStoreLocation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Cert:\CurrentUser\Root&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Method 3: MMC (full control)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Press Win+R, type &lt;code&gt;mmc&lt;/code&gt;, press Enter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File › Add/Remove Snap-in ›&lt;/strong&gt; select &lt;strong&gt;Certificates&lt;/strong&gt;, click Add.&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Computer account&lt;/strong&gt;, click Next, then Finish.&lt;/li&gt;
&lt;li&gt;Expand &lt;strong&gt;Trusted Root Certification Authorities › Certificates&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Right-click &lt;strong&gt;All Tasks › Import…&lt;/strong&gt; — walk through the wizard.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Verify It Worked
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="nb"&gt;certutil&lt;/span&gt; &lt;span class="na"&gt;-store &lt;/span&gt;&lt;span class="kd"&gt;Root&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;findstr&lt;/span&gt; &lt;span class="s2"&gt;"Your Cert CN"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or in PowerShell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Cert:\LocalMachine\Root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Where-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Subject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-domain.local"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart Chrome/Edge and visit your HTTPS site — the padlock should be clean, no warning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Removing the Certificate
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# PowerShell — by thumbprint&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Cert:\LocalMachine\Root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Where-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Subject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your-domain.local"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Remove-Item&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cert installs but browser still shows warning
&lt;/h3&gt;

&lt;p&gt;Fully close Chrome/Edge (check Task Manager) and restart. The trust store is cached in-process.&lt;/p&gt;

&lt;h3&gt;
  
  
  "The parameter is incorrect" during import
&lt;/h3&gt;

&lt;p&gt;The file is probably not a valid X.509 certificate. Check with our &lt;a href="https://cert-depot.com/tools/pem-decoder" rel="noopener noreferrer"&gt;PEM decoder&lt;/a&gt; — if it fails to parse there too, the file is corrupted or the wrong type (maybe a private key?).&lt;/p&gt;

&lt;h3&gt;
  
  
  Certificate has no SAN
&lt;/h3&gt;

&lt;p&gt;Installing the cert as trusted doesn't bypass the SAN check. Browsers will still reject certs without a matching SAN. Regenerate with &lt;a href="https://cert-depot.com/" rel="noopener noreferrer"&gt;our generator&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/trust-self-signed-cert-chrome" rel="noopener noreferrer"&gt;Trust in Chrome (all OSes)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/self-signed-cert-nginx" rel="noopener noreferrer"&gt;Self-signed cert for Nginx&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ssl</category>
      <category>tls</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Trust a Self-Signed Certificate on macOS</title>
      <dc:creator>Dima Stopel</dc:creator>
      <pubDate>Wed, 06 May 2026 12:30:08 +0000</pubDate>
      <link>https://dev.to/dimastopel/how-to-trust-a-self-signed-certificate-on-macos-1dfa</link>
      <guid>https://dev.to/dimastopel/how-to-trust-a-self-signed-certificate-on-macos-1dfa</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://cert-depot.com/guides/trust-self-signed-cert-macos" rel="noopener noreferrer"&gt;cert-depot.com&lt;/a&gt;. Free, open-source self-signed certificate generator — no signup, keys never stored.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  How to Trust a Self-Signed Certificate on macOS
&lt;/h1&gt;

&lt;p&gt;Add your certificate to the macOS System keychain so Safari, Chrome, curl, and most other tools trust it.&lt;/p&gt;

&lt;p&gt;macOS uses a unified keychain system that's shared across most applications — Safari, Chrome, curl, git, and many others. Adding your self-signed certificate once usually makes it trusted everywhere (Firefox is an exception; see our &lt;a href="https://cert-depot.com/guides/trust-self-signed-cert-firefox" rel="noopener noreferrer"&gt;Firefox guide&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  GUI: Keychain Access
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Open &lt;strong&gt;Keychain Access&lt;/strong&gt; (Spotlight: Cmd+Space, type "Keychain").&lt;/li&gt;
&lt;li&gt;From the menu: &lt;strong&gt;File › Import Items&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Select your &lt;code&gt;.pem&lt;/code&gt; or &lt;code&gt;.crt&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;When asked which keychain, choose &lt;strong&gt;System&lt;/strong&gt; (requires admin password). Choose &lt;strong&gt;login&lt;/strong&gt; to trust it only for your user.&lt;/li&gt;
&lt;li&gt;Find the imported certificate, double-click it.&lt;/li&gt;
&lt;li&gt;Expand the &lt;strong&gt;Trust&lt;/strong&gt; section.&lt;/li&gt;
&lt;li&gt;Change "When using this certificate" to &lt;strong&gt;Always Trust&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Close the dialog — you'll be prompted for your password to save.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Command Line
&lt;/h2&gt;

&lt;p&gt;For scripting or CI, use the &lt;code&gt;security&lt;/code&gt; tool. This adds and fully trusts the cert in one step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;security add-trusted-cert &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; trustRoot &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-k&lt;/span&gt; /Library/Keychains/System.keychain /path/to/certificate.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Flags explained: &lt;code&gt;-d&lt;/code&gt; = admin domain, &lt;code&gt;-r trustRoot&lt;/code&gt; = trust as root CA, &lt;code&gt;-k&lt;/code&gt; = keychain path.&lt;/p&gt;

&lt;p&gt;To remove it later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;security delete-certificate &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"Your Certificate CN"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  /Library/Keychains/System.keychain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Verify It Worked
&lt;/h2&gt;

&lt;p&gt;Restart your browser (it caches the trust store at startup). Then use one of these:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Inspect via curl&lt;/span&gt;
curl &lt;span class="nt"&gt;-I&lt;/span&gt; https://your-domain.local/

&lt;span class="c"&gt;# Inspect the trust with security&lt;/span&gt;
security verify-cert &lt;span class="nt"&gt;-c&lt;/span&gt; certificate.pem

&lt;span class="c"&gt;# View the cert that a server is serving&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; | openssl s_client &lt;span class="nt"&gt;-connect&lt;/span&gt; your-domain.local:443 2&amp;gt;/dev/null &lt;span class="se"&gt;\&lt;/span&gt;
  | openssl x509 &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="nt"&gt;-subject&lt;/span&gt; &lt;span class="nt"&gt;-issuer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Common Issues
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Still getting "certificate not trusted" in Chrome
&lt;/h3&gt;

&lt;p&gt;Fully quit Chrome (Cmd+Q) and reopen. Chrome only reads the trust store at startup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Certificate has no SAN
&lt;/h3&gt;

&lt;p&gt;Even when fully trusted in macOS, Chrome and Safari will reject certificates without a Subject Alternative Name matching the hostname. Use our &lt;a href="https://cert-depot.com/" rel="noopener noreferrer"&gt;generator&lt;/a&gt; which includes SANs, or regenerate with &lt;code&gt;openssl&lt;/code&gt; using the &lt;code&gt;-addext&lt;/code&gt; flag.&lt;/p&gt;

&lt;h3&gt;
  
  
  The cert imports but stays "Not Trusted"
&lt;/h3&gt;

&lt;p&gt;You imported it to the login keychain without changing the trust setting. Double-click the cert in Keychain Access and set "Always Trust".&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/trust-self-signed-cert-chrome" rel="noopener noreferrer"&gt;Trust in Chrome (all platforms)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/trust-self-signed-cert-firefox" rel="noopener noreferrer"&gt;Trust in Firefox&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ssl</category>
      <category>tls</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Trust a Self-Signed Certificate in Firefox</title>
      <dc:creator>Dima Stopel</dc:creator>
      <pubDate>Wed, 06 May 2026 12:23:31 +0000</pubDate>
      <link>https://dev.to/dimastopel/how-to-trust-a-self-signed-certificate-in-firefox-223n</link>
      <guid>https://dev.to/dimastopel/how-to-trust-a-self-signed-certificate-in-firefox-223n</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://cert-depot.com/guides/trust-self-signed-cert-firefox" rel="noopener noreferrer"&gt;cert-depot.com&lt;/a&gt;. Free, open-source self-signed certificate generator — no signup, keys never stored.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  How to Trust a Self-Signed Certificate in Firefox
&lt;/h1&gt;

&lt;p&gt;Firefox ignores the OS trust store by default. Here's how to add your cert to Firefox's own store.&lt;/p&gt;

&lt;p&gt;Firefox is the only major browser that maintains its own certificate store separate from the operating system. Installing a cert in Keychain (macOS), Certificate Manager (Windows), or &lt;code&gt;/etc/ssl/certs/&lt;/code&gt; (Linux) won't affect Firefox.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: Enable OS trust store
&lt;/h2&gt;

&lt;p&gt;Since Firefox 49, you can make Firefox honor the OS trust store via a preference:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open &lt;code&gt;about:config&lt;/code&gt; in Firefox.&lt;/li&gt;
&lt;li&gt;Accept the warning.&lt;/li&gt;
&lt;li&gt;Search for &lt;code&gt;security.enterprise_roots.enabled&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set it to &lt;code&gt;true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Restart Firefox.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Firefox will now trust certificates installed in the OS trust store. This is the easiest option if you've already installed the cert OS-wide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2: Import directly into Firefox
&lt;/h2&gt;

&lt;p&gt;Use this if you only want Firefox to trust the cert (not the whole OS), or if the enterprise_roots option isn't working.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open Firefox &lt;strong&gt;Settings&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Search for "certificates".&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;View Certificates…&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Go to the &lt;strong&gt;Authorities&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Import…&lt;/strong&gt;, select your &lt;code&gt;.pem&lt;/code&gt; or &lt;code&gt;.crt&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;Check &lt;strong&gt;Trust this CA to identify websites&lt;/strong&gt;, click OK.&lt;/li&gt;
&lt;li&gt;Restart Firefox (sometimes not required, but safer).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Option 3: Import via the warning page
&lt;/h2&gt;

&lt;p&gt;When Firefox shows "Warning: Potential Security Risk Ahead" for your self-signed site:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click &lt;strong&gt;Advanced…&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Accept the Risk and Continue&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This adds an exception for that specific site, but only until you clear browsing data or the cert changes. For persistent trust, use Options 1 or 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  Command line: certutil (for scripting)
&lt;/h2&gt;

&lt;p&gt;Firefox's cert DB is an NSS database, editable with &lt;code&gt;certutil&lt;/code&gt; from &lt;code&gt;libnss3-tools&lt;/code&gt; (Linux) or the Firefox installation's &lt;code&gt;bin/&lt;/code&gt; directory (macOS/Windows).&lt;/p&gt;

&lt;p&gt;Find the Firefox profile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Linux/macOS&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; ~/.mozilla/firefox/  &lt;span class="c"&gt;# Linux&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; ~/Library/Application&lt;span class="se"&gt;\ &lt;/span&gt;Support/Firefox/Profiles/  &lt;span class="c"&gt;# macOS&lt;/span&gt;
&lt;span class="c"&gt;# Pick the default profile, e.g. abcd1234.default-release&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the cert:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;certutil &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"My Cert"&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"C,,"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-i&lt;/span&gt; certificate.pem &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; ~/.mozilla/firefox/abcd1234.default-release
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-t "C,,"&lt;/code&gt; flag marks it as a trusted CA for SSL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify it worked
&lt;/h2&gt;

&lt;p&gt;Visit your HTTPS site in Firefox. The padlock should be solid (not grayed out or with a warning). Click the padlock and "Connection secure" — it should show your certificate's details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Certificate imports but site still shows warning
&lt;/h3&gt;

&lt;p&gt;You imported it as a server cert instead of a CA. Go back to View Certificates › Authorities (not Your Certificates or Servers) and re-import with the "Trust this CA" checkbox.&lt;/p&gt;

&lt;h3&gt;
  
  
  enterprise_roots doesn't seem to do anything
&lt;/h3&gt;

&lt;p&gt;It only works for certificates marked as "Trusted Root CA" in the OS. If you installed your cert as a regular trusted cert (not a root), Firefox won't pick it up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cert is trusted but Firefox still complains about SANs
&lt;/h3&gt;

&lt;p&gt;Trusting a cert doesn't bypass the SAN check. The certificate must include a Subject Alternative Name matching the hostname you're visiting. Check with our &lt;a href="https://cert-depot.com/tools/pem-decoder" rel="noopener noreferrer"&gt;PEM decoder&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/trust-self-signed-cert-chrome" rel="noopener noreferrer"&gt;Trust in Chrome (all OSes)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/trust-self-signed-cert-macos" rel="noopener noreferrer"&gt;Trust on macOS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/why-sans-matter" rel="noopener noreferrer"&gt;Why SANs matter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ssl</category>
      <category>tls</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Trust a Self-Signed Certificate in Chrome</title>
      <dc:creator>Dima Stopel</dc:creator>
      <pubDate>Wed, 06 May 2026 12:22:00 +0000</pubDate>
      <link>https://dev.to/dimastopel/how-to-trust-a-self-signed-certificate-in-chrome-1lea</link>
      <guid>https://dev.to/dimastopel/how-to-trust-a-self-signed-certificate-in-chrome-1lea</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://cert-depot.com/guides/trust-self-signed-cert-chrome" rel="noopener noreferrer"&gt;cert-depot.com&lt;/a&gt;. Free, open-source self-signed certificate generator — no signup, keys never stored.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  How to Trust a Self-Signed Certificate in Chrome
&lt;/h1&gt;

&lt;p&gt;Remove the browser warning for your development certificates — on any OS.&lt;/p&gt;

&lt;p&gt;Chrome doesn't have its own certificate store — it uses the operating system's trust store. Once you add your self-signed certificate to the OS, Chrome (and most other browsers) will trust it automatically. Firefox is the exception, and we cover it in a &lt;a href="https://cert-depot.com/guides/trust-self-signed-cert-firefox" rel="noopener noreferrer"&gt;separate guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You need the public certificate file (&lt;code&gt;.crt&lt;/code&gt; or &lt;code&gt;.pem&lt;/code&gt;). You do &lt;em&gt;not&lt;/em&gt; need the private key on the client machine — only on the server.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Chrome (since version 58, released in 2017) requires the certificate to have a Subject Alternative Name matching the hostname. If your cert only has a Common Name, it won't be trusted even after you install it. &lt;a href="https://cert-depot.com/" rel="noopener noreferrer"&gt;Our generator&lt;/a&gt; adds SANs automatically.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  macOS
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Double-click the &lt;code&gt;.crt&lt;/code&gt; or &lt;code&gt;.pem&lt;/code&gt; file. Keychain Access will open.&lt;/li&gt;
&lt;li&gt;When prompted, choose the &lt;strong&gt;System&lt;/strong&gt; keychain (not login) so all users trust it.&lt;/li&gt;
&lt;li&gt;Find the certificate in Keychain Access, double-click it.&lt;/li&gt;
&lt;li&gt;Expand &lt;strong&gt;Trust&lt;/strong&gt;, set "When using this certificate" to &lt;strong&gt;Always Trust&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Close the window — you'll be asked for your password to save.&lt;/li&gt;
&lt;li&gt;Quit and restart Chrome.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  From the command line
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;security add-trusted-cert &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; trustRoot &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-k&lt;/span&gt; /Library/Keychains/System.keychain certificate.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Windows
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Double-click the &lt;code&gt;.crt&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Install Certificate&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Local Machine&lt;/strong&gt; (requires admin), click Next.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Place all certificates in the following store&lt;/strong&gt; and browse to &lt;strong&gt;Trusted Root Certification Authorities&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click Next, then Finish. Confirm the security warning.&lt;/li&gt;
&lt;li&gt;Restart Chrome.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  PowerShell
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Import-Certificate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-FilePath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"certificate.crt"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;-CertStoreLocation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Cert:\LocalMachine\Root&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Linux (Ubuntu/Debian)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo cp &lt;/span&gt;certificate.pem /usr/local/share/ca-certificates/my-cert.crt
&lt;span class="nb"&gt;sudo &lt;/span&gt;update-ca-certificates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Fedora/RHEL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo cp &lt;/span&gt;certificate.pem /etc/pki/ca-trust/source/anchors/
&lt;span class="nb"&gt;sudo &lt;/span&gt;update-ca-trust
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Chrome-specific NSS database (Linux only)
&lt;/h3&gt;

&lt;p&gt;Chrome on Linux uses NSS, not the system trust store by default. To trust a cert just for Chrome:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;certutil &lt;span class="nt"&gt;-d&lt;/span&gt; sql:&lt;span class="nv"&gt;$HOME&lt;/span&gt;/.pki/nssdb &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"C,,"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"MyCert"&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; certificate.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install &lt;code&gt;libnss3-tools&lt;/code&gt; (Debian/Ubuntu) or &lt;code&gt;nss-tools&lt;/code&gt; (Fedora) if &lt;code&gt;certutil&lt;/code&gt; is not available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify It Worked
&lt;/h2&gt;

&lt;p&gt;Visit your HTTPS site in Chrome. The padlock should be green (or gray) with no warning. If you still see the warning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fully quit Chrome and restart — it caches the trust store.&lt;/li&gt;
&lt;li&gt;Check that the certificate has a matching SAN using our &lt;a href="https://cert-depot.com/tools/pem-decoder" rel="noopener noreferrer"&gt;PEM decoder&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Confirm the hostname you're visiting exactly matches the SAN entry.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/trust-self-signed-cert-firefox" rel="noopener noreferrer"&gt;Trust in Firefox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/trust-self-signed-cert-macos" rel="noopener noreferrer"&gt;Detailed macOS guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/trust-self-signed-cert-windows" rel="noopener noreferrer"&gt;Detailed Windows guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ssl</category>
      <category>tls</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How Long Should a Self-Signed Certificate Be Valid?</title>
      <dc:creator>Dima Stopel</dc:creator>
      <pubDate>Wed, 06 May 2026 12:14:50 +0000</pubDate>
      <link>https://dev.to/dimastopel/how-long-should-a-self-signed-certificate-be-valid-3hd9</link>
      <guid>https://dev.to/dimastopel/how-long-should-a-self-signed-certificate-be-valid-3hd9</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://cert-depot.com/guides/self-signed-cert-validity-period" rel="noopener noreferrer"&gt;cert-depot.com&lt;/a&gt;. Free, open-source self-signed certificate generator — no signup, keys never stored.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  How Long Should a Self-Signed Certificate Be Valid?
&lt;/h1&gt;

&lt;p&gt;Why 398 days is the public-CA ceiling, why self-signed certs aren't bound by it, and what validity period you should actually pick.&lt;/p&gt;

&lt;p&gt;When generating a self-signed cert, you set how long it's valid. Different tools default to different values: OpenSSL is 30 days, Let's Encrypt is 90 days, enterprise CAs used to issue for 10+ years. Here's how to think about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 398-day browser limit
&lt;/h2&gt;

&lt;p&gt;Since September 2020, &lt;a href="https://support.apple.com/en-us/102028" rel="noopener noreferrer"&gt;Apple&lt;/a&gt;, &lt;a href="https://chromestatus.com/feature/4843772447199232" rel="noopener noreferrer"&gt;Chrome&lt;/a&gt;, and Mozilla reject &lt;strong&gt;publicly-trusted&lt;/strong&gt; SSL certificates valid for more than 398 days. This was a CA/Browser Forum policy change aimed at forcing faster rotation after incidents where CAs kept issuing problematic certs for years.&lt;/p&gt;

&lt;p&gt;This limit applies to certificates chained to a browser-trusted root — in other words, Let's Encrypt, DigiCert, Sectigo, etc. It does &lt;strong&gt;not&lt;/strong&gt; apply to certificates in a private trust chain, like self-signed certs or your own internal CA.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-signed certs can be any duration
&lt;/h2&gt;

&lt;p&gt;Technically, you could issue a self-signed cert valid for 100 years. The browser-limit logic only kicks in for publicly-trusted chains. For dev and internal use, the duration you pick is entirely a tradeoff:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Too short&lt;/strong&gt; — you're constantly regenerating and reinstalling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Too long&lt;/strong&gt; — if the private key leaks, the blast radius is huge.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Practical defaults
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Local development on your laptop: 1–5 years
&lt;/h3&gt;

&lt;p&gt;Set it and forget it. The private key sits on your development machine and rarely leaves it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Internal tools / internal CA: 90 days to 1 year
&lt;/h3&gt;

&lt;p&gt;Short enough to build rotation hygiene, long enough that you're not rotating weekly.&lt;/p&gt;

&lt;h3&gt;
  
  
  CI/CD or ephemeral test environments: 30–90 days
&lt;/h3&gt;

&lt;p&gt;Matches the rotation cadence of the infrastructure it lives on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Certificate authority root (your own internal CA): 10–20 years
&lt;/h3&gt;

&lt;p&gt;Rotating a root is painful. Pick a long duration and protect the private key in a vault.&lt;/p&gt;

&lt;h2&gt;
  
  
  What browsers will accept from self-signed sources
&lt;/h2&gt;

&lt;p&gt;A self-signed cert valid for 10 years is technically fine. But if a user decides to "trust" a 10-year cert in their OS, they're granting that cert authority for 10 years. Consider what happens if the server holding the private key is compromised in year 3.&lt;/p&gt;

&lt;p&gt;For maximum safety, 1 year is a good ceiling. Renew annually with a script.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading the validity period of an existing cert
&lt;/h2&gt;

&lt;p&gt;Use our &lt;a href="https://cert-depot.com/tools/pem-decoder" rel="noopener noreferrer"&gt;PEM decoder&lt;/a&gt;, or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl x509 &lt;span class="nt"&gt;-in&lt;/span&gt; cert.pem &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="nt"&gt;-dates&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;notBefore=Apr  1 12:00:00 2026 GMT
notAfter=Apr  1 12:00:00 2027 GMT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/rsa-vs-ecdsa-self-signed" rel="noopener noreferrer"&gt;Picking the key algorithm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/self-signed-cert-nginx" rel="noopener noreferrer"&gt;Configure on Nginx&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ssl</category>
      <category>tls</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Self-Signed SSL Certificate for Node.js HTTPS Server</title>
      <dc:creator>Dima Stopel</dc:creator>
      <pubDate>Wed, 06 May 2026 12:13:20 +0000</pubDate>
      <link>https://dev.to/dimastopel/self-signed-ssl-certificate-for-nodejs-https-server-mcg</link>
      <guid>https://dev.to/dimastopel/self-signed-ssl-certificate-for-nodejs-https-server-mcg</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://cert-depot.com/guides/self-signed-cert-nodejs" rel="noopener noreferrer"&gt;cert-depot.com&lt;/a&gt;. Free, open-source self-signed certificate generator — no signup, keys never stored.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Self-Signed SSL Certificate for Node.js HTTPS Server
&lt;/h1&gt;

&lt;p&gt;Run a Node.js HTTPS server with a self-signed certificate — native, Express, and Fastify examples.&lt;/p&gt;

&lt;p&gt;Node.js has built-in HTTPS support via the &lt;code&gt;https&lt;/code&gt; module. You just need to load your certificate and key, and pass them as options to &lt;code&gt;createServer&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Generate the Certificate
&lt;/h2&gt;

&lt;p&gt;Use &lt;a href="https://cert-depot.com/" rel="noopener noreferrer"&gt;cert-depot.com&lt;/a&gt; for a browser-friendly cert, or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl req &lt;span class="nt"&gt;-x509&lt;/span&gt; &lt;span class="nt"&gt;-newkey&lt;/span&gt; rsa:2048 &lt;span class="nt"&gt;-nodes&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-keyout&lt;/span&gt; key.pem &lt;span class="nt"&gt;-out&lt;/span&gt; cert.pem &lt;span class="nt"&gt;-days&lt;/span&gt; 365 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-subj&lt;/span&gt; &lt;span class="s2"&gt;"/CN=localhost"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-addext&lt;/span&gt; &lt;span class="s2"&gt;"subjectAltName=DNS:localhost,IP:127.0.0.1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Native https module
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;https&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:https&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;key.pem&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;cert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cert.pem&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/plain&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello over TLS!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3443&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Listening on https://localhost:3443/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Express
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;https&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:https&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello over TLS!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;key.pem&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;cert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cert.pem&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3443&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Express listening on https://localhost:3443/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Fastify
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fastify&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fastify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)({&lt;/span&gt;
    &lt;span class="na"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;key.pem&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;cert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cert.pem&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;world&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3443&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Making fetch / axios / node-fetch trust the certificate
&lt;/h2&gt;

&lt;p&gt;By default, Node.js rejects self-signed certificates when acting as a client. For development only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run with NODE_TLS_REJECT_UNAUTHORIZED=0 — insecure, dev only&lt;/span&gt;
&lt;span class="nv"&gt;NODE_TLS_REJECT_UNAUTHORIZED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 node client.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A better approach: point Node.js at the cert via &lt;code&gt;NODE_EXTRA_CA_CERTS&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;NODE_EXTRA_CA_CERTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./cert.pem node client.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or pass it per-request with the &lt;code&gt;ca&lt;/code&gt; option in &lt;code&gt;https.Agent&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Never&lt;/strong&gt; set &lt;code&gt;NODE_TLS_REJECT_UNAUTHORIZED=0&lt;/code&gt; in production. It disables all certificate validation, making every HTTPS call vulnerable to MitM attacks. Use &lt;code&gt;NODE_EXTRA_CA_CERTS&lt;/code&gt; instead.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  PFX / PKCS#12 alternative
&lt;/h2&gt;

&lt;p&gt;Instead of separate key and cert, you can load a single PFX file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;pfx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cert.pfx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;passphrase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3443&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://cert-depot.com/" rel="noopener noreferrer"&gt;Our generator&lt;/a&gt; produces PFX files directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/self-signed-cert-nginx" rel="noopener noreferrer"&gt;Nginx setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/rsa-vs-ecdsa-self-signed" rel="noopener noreferrer"&gt;RSA vs ECDSA&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ssl</category>
      <category>tls</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Self-Signed Certificates for localhost Development</title>
      <dc:creator>Dima Stopel</dc:creator>
      <pubDate>Wed, 06 May 2026 12:06:43 +0000</pubDate>
      <link>https://dev.to/dimastopel/self-signed-certificates-for-localhost-development-2m3o</link>
      <guid>https://dev.to/dimastopel/self-signed-certificates-for-localhost-development-2m3o</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://cert-depot.com/guides/self-signed-cert-localhost-dev" rel="noopener noreferrer"&gt;cert-depot.com&lt;/a&gt;. Free, open-source self-signed certificate generator — no signup, keys never stored.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Self-Signed Certificates for localhost Development
&lt;/h1&gt;

&lt;p&gt;Stop clicking past browser warnings. Get a proper green-padlock HTTPS localhost in a few minutes.&lt;/p&gt;

&lt;p&gt;Most modern web APIs require HTTPS: Service Workers, Web Crypto, geolocation, camera access, OAuth flows, HTTP/2. Developing locally against HTTP hits constant "feature unavailable" errors. A self-signed cert for &lt;code&gt;localhost&lt;/code&gt; fixes this — but only if you trust it properly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Generate the certificate
&lt;/h2&gt;

&lt;p&gt;With &lt;a href="https://cert-depot.com/" rel="noopener noreferrer"&gt;cert-depot.com&lt;/a&gt;: enter &lt;code&gt;localhost&lt;/code&gt; as the Common Name, add &lt;code&gt;127.0.0.1&lt;/code&gt; and &lt;code&gt;::1&lt;/code&gt; as IP SANs, generate.&lt;/p&gt;

&lt;p&gt;With OpenSSL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl req &lt;span class="nt"&gt;-x509&lt;/span&gt; &lt;span class="nt"&gt;-newkey&lt;/span&gt; rsa:2048 &lt;span class="nt"&gt;-nodes&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-keyout&lt;/span&gt; localhost.key &lt;span class="nt"&gt;-out&lt;/span&gt; localhost.crt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-days&lt;/span&gt; 365 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-subj&lt;/span&gt; &lt;span class="s2"&gt;"/CN=localhost"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-addext&lt;/span&gt; &lt;span class="s2"&gt;"subjectAltName=DNS:localhost,IP:127.0.0.1,IP:::1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Trust it in your OS
&lt;/h2&gt;

&lt;p&gt;This is the step that makes the browser warning go away. Without it, you still get HTTPS (encrypted traffic), but the browser shows "Not Secure" and some APIs stay blocked.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/trust-self-signed-cert-macos" rel="noopener noreferrer"&gt;macOS — Keychain Access&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/trust-self-signed-cert-windows" rel="noopener noreferrer"&gt;Windows — Trusted Root store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/trust-self-signed-cert-chrome" rel="noopener noreferrer"&gt;Chrome-specific NSS store on Linux&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 3: Serve it from your dev server
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Vite
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// vite.config.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;localhost.key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="na"&gt;cert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;localhost.crt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Next.js (via custom server)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createServer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRequestHandler&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;localhost.key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;cert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;localhost.crt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  webpack-dev-server
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// webpack.config.js&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;devServer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./localhost.key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;cert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./localhost.crt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Python (http.server replacement)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;http.server&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HTTPServer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SimpleHTTPRequestHandler&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ssl&lt;/span&gt;

&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SSLContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROTOCOL_TLS_SERVER&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_cert_chain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost.crt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost.key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HTTPServer&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4443&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;SimpleHTTPRequestHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wrap_socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;server_side&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serve_forever&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Alternative: mkcert
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/FiloSottile/mkcert" rel="noopener noreferrer"&gt;mkcert&lt;/a&gt; automates the trust step by installing a local CA, then issuing certs from that CA. Install with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;mkcert  &lt;span class="c"&gt;# macOS&lt;/span&gt;
mkcert &lt;span class="nt"&gt;-install&lt;/span&gt;
mkcert localhost 127.0.0.1 ::1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's convenient. The tradeoff is that mkcert installs its own root CA on your machine — meaning anyone who gets the mkcert CA private key could issue trusted certs for any domain. For a personal laptop this is fine; for shared machines or CI, prefer explicit per-cert trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not use a real cert for localhost?
&lt;/h2&gt;

&lt;p&gt;You can't. Let's Encrypt (and every other publicly-trusted CA) refuses to issue certs for &lt;code&gt;localhost&lt;/code&gt;, private IP ranges, or any domain they can't validate from the public internet. Self-signed is the only option.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/trust-self-signed-cert-macos" rel="noopener noreferrer"&gt;Trust on macOS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/trust-self-signed-cert-windows" rel="noopener noreferrer"&gt;Trust on Windows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/self-signed-cert-nodejs" rel="noopener noreferrer"&gt;Node.js HTTPS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ssl</category>
      <category>tls</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Self-Signed SSL Certificates in Docker</title>
      <dc:creator>Dima Stopel</dc:creator>
      <pubDate>Wed, 06 May 2026 12:05:12 +0000</pubDate>
      <link>https://dev.to/dimastopel/self-signed-ssl-certificates-in-docker-3o30</link>
      <guid>https://dev.to/dimastopel/self-signed-ssl-certificates-in-docker-3o30</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://cert-depot.com/guides/self-signed-cert-docker" rel="noopener noreferrer"&gt;cert-depot.com&lt;/a&gt;. Free, open-source self-signed certificate generator — no signup, keys never stored.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Self-Signed SSL Certificates in Docker
&lt;/h1&gt;

&lt;p&gt;Mount certs, build them into images, or trust them inside a container — all the patterns for self-signed certs in Docker.&lt;/p&gt;

&lt;p&gt;Running a self-signed cert in a container is the same as on bare metal — you just need the files where the process can read them. A few patterns cover 99% of use cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 1: Mount certs as a volume (recommended)
&lt;/h2&gt;

&lt;p&gt;Keeps certs out of the image. Rotate them without rebuilding.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;nginx&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:alpine&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;443:443"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./certs:/etc/nginx/ssl:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./nginx.conf:/etc/nginx/nginx.conf:ro&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in &lt;code&gt;nginx.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;ssl_certificate&lt;/span&gt;     &lt;span class="n"&gt;/etc/nginx/ssl/certificate.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ssl_certificate_key&lt;/span&gt; &lt;span class="n"&gt;/etc/nginx/ssl/private-key.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pattern 2: Docker secrets
&lt;/h2&gt;

&lt;p&gt;For Docker Swarm, put certs in secrets. They're mounted at &lt;code&gt;/run/secrets/&lt;/code&gt; read-only inside the container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml&lt;/span&gt;
&lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;cert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./certificate.pem&lt;/span&gt;
  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./private-key.pem&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;nginx&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:alpine&lt;/span&gt;
    &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;cert&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;# reference as /run/secrets/cert and /run/secrets/key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pattern 3: Build certs into the image (dev only)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; nginx:alpine&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; certificate.pem /etc/nginx/ssl/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; private-key.pem /etc/nginx/ssl/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; nginx.conf /etc/nginx/nginx.conf&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Never&lt;/strong&gt; ship a container image to a registry with the private key baked in. Anyone who pulls the image can extract the key. Use mount or secrets for anything that leaves your laptop.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Trust a self-signed cert inside the container
&lt;/h2&gt;

&lt;p&gt;If your app runs in a container and needs to call an HTTPS service with a self-signed cert:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Dockerfile — Debian/Ubuntu-based&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ca.pem /usr/local/share/ca-certificates/my-ca.crt&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;update-ca-certificates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alpine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ca.pem /usr/local/share/ca-certificates/my-ca.crt&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; ca-certificates &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; update-ca-certificates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pattern 4: Caddy (auto-HTTPS in a container)
&lt;/h2&gt;

&lt;p&gt;For pure local dev, Caddy will generate a self-signed cert on the fly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Caddyfile
localhost {
    reverse_proxy app:3000
    tls internal
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;tls internal&lt;/code&gt; tells Caddy to mint a cert from its internal CA. Trust Caddy's CA in your OS once, and everything it serves is trusted for the duration of the cert.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  "SSL_CTX_use_PrivateKey_file failed"
&lt;/h3&gt;

&lt;p&gt;The container can't read the key file. Check volume mount permissions, and make sure the file isn't empty (a common silent failure when a volume path is wrong — it creates an empty directory instead of mounting the file).&lt;/p&gt;

&lt;h3&gt;
  
  
  Client inside container can't verify a self-signed server
&lt;/h3&gt;

&lt;p&gt;You need to trust the cert inside the container — see the &lt;code&gt;update-ca-certificates&lt;/code&gt; pattern above. For Node.js containers, mount the cert and set &lt;code&gt;NODE_EXTRA_CA_CERTS&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/self-signed-cert-nginx" rel="noopener noreferrer"&gt;Nginx setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/self-signed-cert-nodejs" rel="noopener noreferrer"&gt;Node.js HTTPS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ssl</category>
      <category>tls</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Self-Signed SSL Certificate for Apache HTTP Server</title>
      <dc:creator>Dima Stopel</dc:creator>
      <pubDate>Wed, 06 May 2026 11:58:36 +0000</pubDate>
      <link>https://dev.to/dimastopel/self-signed-ssl-certificate-for-apache-http-server-38g3</link>
      <guid>https://dev.to/dimastopel/self-signed-ssl-certificate-for-apache-http-server-38g3</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://cert-depot.com/guides/self-signed-cert-apache" rel="noopener noreferrer"&gt;cert-depot.com&lt;/a&gt;. Free, open-source self-signed certificate generator — no signup, keys never stored.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Self-Signed SSL Certificate for Apache HTTP Server
&lt;/h1&gt;

&lt;p&gt;Configure Apache to serve HTTPS using a self-signed certificate.&lt;/p&gt;

&lt;p&gt;Apache HTTP Server uses &lt;code&gt;mod_ssl&lt;/code&gt; for HTTPS. Once the module is enabled, serving a self-signed certificate is just three config directives and a reload.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Generate the Certificate
&lt;/h2&gt;

&lt;p&gt;Use &lt;a href="https://cert-depot.com/" rel="noopener noreferrer"&gt;cert-depot.com&lt;/a&gt; for a browser-friendly cert with Subject Alternative Names, or generate one with OpenSSL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl req &lt;span class="nt"&gt;-x509&lt;/span&gt; &lt;span class="nt"&gt;-newkey&lt;/span&gt; rsa:2048 &lt;span class="nt"&gt;-nodes&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-keyout&lt;/span&gt; server.key &lt;span class="nt"&gt;-out&lt;/span&gt; server.crt &lt;span class="nt"&gt;-days&lt;/span&gt; 365 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-subj&lt;/span&gt; &lt;span class="s2"&gt;"/CN=example.local"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-addext&lt;/span&gt; &lt;span class="s2"&gt;"subjectAltName=DNS:example.local,IP:127.0.0.1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Enable mod_ssl
&lt;/h2&gt;

&lt;p&gt;On Debian/Ubuntu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;a2enmod ssl
&lt;span class="nb"&gt;sudo &lt;/span&gt;a2ensite default-ssl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On RHEL/CentOS, install it if it's not already present:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install &lt;/span&gt;mod_ssl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Install the Certificate Files
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /etc/apache2/ssl  &lt;span class="c"&gt;# or /etc/httpd/conf.d/ssl on RHEL&lt;/span&gt;
&lt;span class="nb"&gt;sudo cp &lt;/span&gt;server.crt /etc/apache2/ssl/
&lt;span class="nb"&gt;sudo cp &lt;/span&gt;server.key /etc/apache2/ssl/
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;600 /etc/apache2/ssl/server.key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Configure the VirtualHost
&lt;/h2&gt;

&lt;p&gt;Edit &lt;code&gt;/etc/apache2/sites-available/default-ssl.conf&lt;/code&gt; (Debian/Ubuntu) or &lt;code&gt;/etc/httpd/conf.d/ssl.conf&lt;/code&gt; (RHEL):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nl"&gt;VirtualHost&lt;/span&gt;&lt;span class="sr"&gt; *:443&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="nc"&gt;ServerName&lt;/span&gt; example.local
    &lt;span class="nc"&gt;DocumentRoot&lt;/span&gt; /var/www/html

    &lt;span class="nc"&gt;SSLEngine&lt;/span&gt; &lt;span class="ss"&gt;on&lt;/span&gt;
    &lt;span class="nc"&gt;SSLCertificateFile&lt;/span&gt;    /etc/apache2/ssl/server.crt
    &lt;span class="nc"&gt;SSLCertificateKeyFile&lt;/span&gt; /etc/apache2/ssl/server.key

    &lt;span class="nc"&gt;SSLProtocol&lt;/span&gt; -all +TLSv1.2 +TLSv1.3
    &lt;span class="nc"&gt;SSLCipherSuite&lt;/span&gt; HIGH:!aNULL:!MD5
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nl"&gt;VirtualHost&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; If you need HTTP to redirect to HTTPS, add a &lt;code&gt;&amp;lt;VirtualHost *:80&amp;gt;&lt;/code&gt; block with &lt;code&gt;Redirect permanent / https://example.local/&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 5: Test and Reload
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apachectl configtest
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reload apache2  &lt;span class="c"&gt;# or httpd on RHEL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify with &lt;code&gt;curl -kvI https://example.local/&lt;/code&gt; — you should see the TLS handshake succeed and the certificate details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  "SSL Library Error: Unable to load private key"
&lt;/h3&gt;

&lt;p&gt;Wrong file permissions or the key is password-protected. Use &lt;code&gt;-nodes&lt;/code&gt; when generating with OpenSSL to produce an unencrypted key (safe for development only).&lt;/p&gt;

&lt;h3&gt;
  
  
  "AH00526: Syntax error"
&lt;/h3&gt;

&lt;p&gt;Run &lt;code&gt;apachectl configtest&lt;/code&gt; — it will point to the exact line. Usually a missing closing tag or typo in a file path.&lt;/p&gt;

&lt;h3&gt;
  
  
  Chrome shows ERR_CERT_COMMON_NAME_INVALID
&lt;/h3&gt;

&lt;p&gt;Your certificate is missing a Subject Alternative Name. Regenerate it with the &lt;code&gt;-addext "subjectAltName=..."&lt;/code&gt; flag, or use our &lt;a href="https://cert-depot.com/" rel="noopener noreferrer"&gt;generator&lt;/a&gt; which includes SANs automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/self-signed-cert-nginx" rel="noopener noreferrer"&gt;Self-signed certificate for Nginx&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/trust-self-signed-cert-chrome" rel="noopener noreferrer"&gt;Trust a self-signed certificate in Chrome&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/why-sans-matter" rel="noopener noreferrer"&gt;Why Subject Alternative Names matter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ssl</category>
      <category>tls</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>RSA vs ECDSA for Self-Signed Certificates</title>
      <dc:creator>Dima Stopel</dc:creator>
      <pubDate>Wed, 06 May 2026 11:57:05 +0000</pubDate>
      <link>https://dev.to/dimastopel/rsa-vs-ecdsa-for-self-signed-certificates-4geo</link>
      <guid>https://dev.to/dimastopel/rsa-vs-ecdsa-for-self-signed-certificates-4geo</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://cert-depot.com/guides/rsa-vs-ecdsa-self-signed" rel="noopener noreferrer"&gt;cert-depot.com&lt;/a&gt;. Free, open-source self-signed certificate generator — no signup, keys never stored.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  RSA vs ECDSA for Self-Signed Certificates
&lt;/h1&gt;

&lt;p&gt;Which key algorithm should you pick? Short answer: ECDSA P-256 if everything you're integrating with supports it. RSA 2048 otherwise.&lt;/p&gt;

&lt;p&gt;When you generate a self-signed certificate, you pick a key algorithm. &lt;a href="https://cert-depot.com/" rel="noopener noreferrer"&gt;Our generator&lt;/a&gt; offers RSA 2048, RSA 4096, ECDSA P-256, and ECDSA P-384. Here's how to choose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ECDSA P-256&lt;/strong&gt; — default for new services. Smaller, faster, cryptographically equivalent to RSA 3072.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RSA 2048&lt;/strong&gt; — maximum compatibility. If you don't know what will consume the cert, use this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RSA 4096&lt;/strong&gt; — only if a compliance checklist demands it. Slower, larger, no meaningful security advantage over RSA 3072 for normal use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ECDSA P-384&lt;/strong&gt; — for high-assurance scenarios. Overkill for most self-signed certs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Speed
&lt;/h2&gt;

&lt;p&gt;ECDSA is dramatically faster for signing. On a modern CPU:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RSA 2048 sign: ~1 ms&lt;/li&gt;
&lt;li&gt;RSA 4096 sign: ~6 ms (6x slower)&lt;/li&gt;
&lt;li&gt;ECDSA P-256 sign: ~0.04 ms (25x faster than RSA 2048)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a TLS handshake, this matters on high-traffic servers. For a self-signed cert in development, it's immaterial.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Size and Certificate Size
&lt;/h2&gt;

&lt;p&gt;ECDSA keys are tiny compared to RSA:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RSA 2048: public key is ~294 bytes, cert is ~1.2 KB&lt;/li&gt;
&lt;li&gt;RSA 4096: public key is ~550 bytes, cert is ~1.8 KB&lt;/li&gt;
&lt;li&gt;ECDSA P-256: public key is ~91 bytes, cert is ~700 bytes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Smaller certs = faster TLS handshakes, especially on mobile.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Equivalence
&lt;/h2&gt;

&lt;p&gt;NIST publishes &lt;a href="https://www.keylength.com/en/4/" rel="noopener noreferrer"&gt;equivalent strength tables&lt;/a&gt;. Summary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ECDSA P-256 ≈ RSA 3072 (128-bit security)&lt;/li&gt;
&lt;li&gt;ECDSA P-384 ≈ RSA 7680 (192-bit security)&lt;/li&gt;
&lt;li&gt;RSA 2048 ≈ 112-bit security (acceptable through ~2030)&lt;/li&gt;
&lt;li&gt;RSA 4096 ≈ 140-bit security&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both families resist all currently known classical attacks at these sizes. Both are broken by a sufficiently large quantum computer, which doesn't exist yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compatibility
&lt;/h2&gt;

&lt;p&gt;RSA is universally supported. Every SSL/TLS client ever built speaks RSA.&lt;/p&gt;

&lt;p&gt;ECDSA is supported by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All modern browsers (Chrome, Safari, Firefox, Edge — full support since ~2013)&lt;/li&gt;
&lt;li&gt;curl, wget, OpenSSL, GnuTLS, BoringSSL&lt;/li&gt;
&lt;li&gt;Go, Rust, Node.js, Python, Java 8+, .NET Core&lt;/li&gt;
&lt;li&gt;All recent Linux distributions, macOS 10.9+, Windows 7+&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ECDSA is &lt;em&gt;not&lt;/em&gt; supported by some very old embedded devices, certain industrial control systems, Java before 7, and some legacy load balancer firmware. If you're integrating with anything in that list, choose RSA.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Recommendations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Local development, modern stack
&lt;/h3&gt;

&lt;p&gt;Use &lt;strong&gt;ECDSA P-256&lt;/strong&gt;. It's faster, smaller, and everything you care about supports it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enterprise / legacy integration
&lt;/h3&gt;

&lt;p&gt;Use &lt;strong&gt;RSA 2048&lt;/strong&gt;. You don't know what 20-year-old appliance will encounter the cert; RSA is the safe bet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compliance requires 4096+
&lt;/h3&gt;

&lt;p&gt;Use &lt;strong&gt;RSA 4096&lt;/strong&gt; or &lt;strong&gt;ECDSA P-384&lt;/strong&gt;. The compliance requirement is usually a checkbox, not a cryptographic necessity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Air-gapped / future-proofing
&lt;/h3&gt;

&lt;p&gt;Neither is quantum-safe. When post-quantum crypto is standardized (ML-DSA etc.), you'll want to regenerate anyway. Don't pick an algorithm based on quantum threats today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/self-signed-cert-nginx" rel="noopener noreferrer"&gt;Configure your cert on Nginx&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/why-sans-matter" rel="noopener noreferrer"&gt;Why SANs matter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ssl</category>
      <category>tls</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Convert PEM to PFX (PKCS#12) with OpenSSL</title>
      <dc:creator>Dima Stopel</dc:creator>
      <pubDate>Wed, 06 May 2026 11:50:35 +0000</pubDate>
      <link>https://dev.to/dimastopel/convert-pem-to-pfx-pkcs12-with-openssl-2a9d</link>
      <guid>https://dev.to/dimastopel/convert-pem-to-pfx-pkcs12-with-openssl-2a9d</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://cert-depot.com/guides/convert-pem-to-pfx-openssl" rel="noopener noreferrer"&gt;cert-depot.com&lt;/a&gt;. Free, open-source self-signed certificate generator — no signup, keys never stored.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Convert PEM to PFX (PKCS#12) with OpenSSL
&lt;/h1&gt;

&lt;p&gt;Bundle your cert and key into a single password-protected PFX file — the format Windows, IIS, and Java expect.&lt;/p&gt;

&lt;p&gt;PEM (two files: &lt;code&gt;.crt&lt;/code&gt; + &lt;code&gt;.key&lt;/code&gt;) is the native format on Linux and most open-source servers. PFX (also called PKCS#12, &lt;code&gt;.pfx&lt;/code&gt;, or &lt;code&gt;.p12&lt;/code&gt;) bundles both into a single password-protected file — required by IIS, Windows Certificate Store, and Java keystores.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic conversion
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl pkcs12 &lt;span class="nt"&gt;-export&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-out&lt;/span&gt; certificate.pfx &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-inkey&lt;/span&gt; private-key.pem &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-in&lt;/span&gt; certificate.pem &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"My Certificate"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OpenSSL will prompt for a password. Enter something non-empty — Windows refuses to import PFX files with empty passwords.&lt;/p&gt;

&lt;h2&gt;
  
  
  Including a CA chain
&lt;/h2&gt;

&lt;p&gt;If your cert has a chain (intermediate CA certs), include them with &lt;code&gt;-certfile&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl pkcs12 &lt;span class="nt"&gt;-export&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-out&lt;/span&gt; certificate.pfx &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-inkey&lt;/span&gt; private-key.pem &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-in&lt;/span&gt; certificate.pem &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-certfile&lt;/span&gt; ca-chain.pem &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"My Certificate"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Scripting (non-interactive)
&lt;/h2&gt;

&lt;p&gt;To avoid the password prompt, pass it via &lt;code&gt;-passout&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl pkcs12 &lt;span class="nt"&gt;-export&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-out&lt;/span&gt; certificate.pfx &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-inkey&lt;/span&gt; private-key.pem &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-in&lt;/span&gt; certificate.pem &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-password&lt;/span&gt; pass:MyPassword123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Security warning:&lt;/strong&gt; Using &lt;code&gt;pass:&lt;/code&gt; puts the password in your shell history and process list. Prefer &lt;code&gt;file:/path/to/passwordfile&lt;/code&gt; or &lt;code&gt;env:VARNAME&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Skip the conversion entirely
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://cert-depot.com/" rel="noopener noreferrer"&gt;Our generator&lt;/a&gt; can output PFX directly. Pick "PFX" as the output format, enter a password, and get a ready-to-import file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reverse: PFX to PEM
&lt;/h2&gt;

&lt;p&gt;Extract the certificate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl pkcs12 &lt;span class="nt"&gt;-in&lt;/span&gt; certificate.pfx &lt;span class="nt"&gt;-clcerts&lt;/span&gt; &lt;span class="nt"&gt;-nokeys&lt;/span&gt; &lt;span class="nt"&gt;-out&lt;/span&gt; certificate.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Extract the private key (unencrypted — dev only):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl pkcs12 &lt;span class="nt"&gt;-in&lt;/span&gt; certificate.pfx &lt;span class="nt"&gt;-nocerts&lt;/span&gt; &lt;span class="nt"&gt;-nodes&lt;/span&gt; &lt;span class="nt"&gt;-out&lt;/span&gt; private-key.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Extract the CA chain only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl pkcs12 &lt;span class="nt"&gt;-in&lt;/span&gt; certificate.pfx &lt;span class="nt"&gt;-cacerts&lt;/span&gt; &lt;span class="nt"&gt;-nokeys&lt;/span&gt; &lt;span class="nt"&gt;-out&lt;/span&gt; ca-chain.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Modern vs legacy PKCS#12
&lt;/h2&gt;

&lt;p&gt;OpenSSL 3+ uses modern PKCS#12 encryption (AES-256) by default, which some older Windows versions can't read. If IIS or a legacy Windows tool refuses to import your PFX, regenerate with legacy encryption:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl pkcs12 &lt;span class="nt"&gt;-export&lt;/span&gt; &lt;span class="nt"&gt;-legacy&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-out&lt;/span&gt; certificate.pfx &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-inkey&lt;/span&gt; private-key.pem &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-in&lt;/span&gt; certificate.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  "Error unable to get local issuer certificate"
&lt;/h3&gt;

&lt;p&gt;Your cert is signed by a chain your OpenSSL can't resolve. Use &lt;code&gt;-certfile&lt;/code&gt; to supply the chain, or use &lt;code&gt;-CAfile /etc/ssl/certs/ca-certificates.crt&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  "Mac verify error"
&lt;/h3&gt;

&lt;p&gt;Wrong password when decrypting a PFX. If you don't know the password, there's no recovery — you'll need the original PEM files.&lt;/p&gt;

&lt;h3&gt;
  
  
  The PFX imports but is "not associated with a private key"
&lt;/h3&gt;

&lt;p&gt;The private key didn't match the certificate's public key. Check with our &lt;a href="https://cert-depot.com/tools/pem-decoder" rel="noopener noreferrer"&gt;PEM decoder&lt;/a&gt; — if the public key differs from what your key computes, they're not a matching pair.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/self-signed-cert-nginx" rel="noopener noreferrer"&gt;Nginx setup (uses PEM)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cert-depot.com/guides/trust-self-signed-cert-windows" rel="noopener noreferrer"&gt;Trust on Windows (often uses PFX)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ssl</category>
      <category>tls</category>
      <category>security</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
