<?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>Forem: Emily Flias</title>
    <description>The latest articles on Forem by Emily Flias (@emily-flias).</description>
    <link>https://forem.com/emily-flias</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%2F1072045%2Fd6155525-ff86-4677-a0a3-77184d047f92.jpg</url>
      <title>Forem: Emily Flias</title>
      <link>https://forem.com/emily-flias</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/emily-flias"/>
    <language>en</language>
    <item>
      <title>How can I set up SSL on https://localhost/ [Updated 2026]</title>
      <dc:creator>Emily Flias</dc:creator>
      <pubDate>Wed, 26 Apr 2023 09:32:33 +0000</pubDate>
      <link>https://forem.com/emily-flias/how-can-i-set-up-ssl-on-localhost-httpslocalhost-3f4g</link>
      <guid>https://forem.com/emily-flias/how-can-i-set-up-ssl-on-localhost-httpslocalhost-3f4g</guid>
      <description>&lt;p&gt;Before we start, it's important to note that SSL certificates are typically issued for domain names, not IP addresses or localhost. &lt;br&gt;
To serve &lt;a href="https://localhost.co/" rel="noopener noreferrer"&gt;https://localhost/&lt;/a&gt; in a way that modern browsers accept (no red warning), you need two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A certificate whose &lt;strong&gt;Subject Alternative Name (SAN)&lt;/strong&gt; includes &lt;code&gt;localhost&lt;/code&gt; (and often &lt;code&gt;127.0.0.1&lt;/code&gt; and &lt;code&gt;::1&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;A way for your OS/browser to &lt;strong&gt;trust&lt;/strong&gt; the issuing CA (or the certificate itself).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The most reliable developer workflow is to create a &lt;strong&gt;local, trusted CA&lt;/strong&gt; and issue a cert for &lt;code&gt;localhost&lt;/code&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  Option A (recommended): &lt;code&gt;mkcert&lt;/code&gt; (trusted HTTPS with minimal friction)
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;mkcert&lt;/code&gt; creates a local certificate authority (CA), installs it into your system trust store, then issues certs for your local domains.&lt;/p&gt;
&lt;h3&gt;
  
  
  1) Install &lt;code&gt;mkcert&lt;/code&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;macOS (Homebrew)&lt;/strong&gt;: &lt;code&gt;brew install mkcert&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windows (Chocolatey)&lt;/strong&gt;: &lt;code&gt;choco install mkcert&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windows (Scoop)&lt;/strong&gt;: &lt;code&gt;scoop bucket add extras &amp;amp;&amp;amp; scoop install mkcert&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linux&lt;/strong&gt;: install from your distro packages or the project releases; also ensure &lt;code&gt;nss&lt;/code&gt; tooling is installed for Firefox trust integration (varies by distro).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  2) Create and trust a local CA
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mkcert &lt;span class="nt"&gt;-install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  3) Generate a cert for localhost
&lt;/h3&gt;

&lt;p&gt;Run this where you want the files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mkcert localhost 127.0.0.1 ::1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This produces files like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;localhost+2.pem&lt;/code&gt; (certificate)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;localhost+2-key.pem&lt;/code&gt; (private key)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4) Configure your local server to use the cert
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Node.js (Express or plain HTTPS)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;https&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;https&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&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;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;import&lt;/span&gt; &lt;span class="nx"&gt;app&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;./app.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// your Express app&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="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+2-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;./localhost+2.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;443&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="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;HTTPS on https://localhost&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you don’t want root/admin privileges for port 443, use 8443 and browse &lt;code&gt;https://localhost:8443/&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Python (Flask)
&lt;/h4&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;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ssl_context&lt;/span&gt;&lt;span class="o"&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+2.pem&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+2-key.pem&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8443&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Uvicorn / FastAPI
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uvicorn main:app &lt;span class="nt"&gt;--host&lt;/span&gt; 127.0.0.1 &lt;span class="nt"&gt;--port&lt;/span&gt; 8443 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--ssl-keyfile&lt;/span&gt; localhost+2-key.pem &lt;span class="nt"&gt;--ssl-certfile&lt;/span&gt; localhost+2.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Nginx (reverse proxy)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kn"&gt;ssl_certificate&lt;/span&gt;     &lt;span class="n"&gt;/path/to/localhost+2.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;ssl_certificate_key&lt;/span&gt; &lt;span class="n"&gt;/path/to/localhost+2-key.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://127.0.0.1:3000&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;h2&gt;
  
  
  Option B: Caddy (very convenient local HTTPS)
&lt;/h2&gt;

&lt;p&gt;Caddy can generate and manage a local CA automatically and serve HTTPS locally with minimal configuration.&lt;/p&gt;

&lt;p&gt;Example &lt;code&gt;Caddyfile&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;localhost {
  reverse_proxy 127.0.0.1:3000
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run Caddy and it will handle certs for you (you may need to trust Caddy’s local CA depending on platform prompts).&lt;/p&gt;




&lt;h2&gt;
  
  
  Option C: OpenSSL self-signed cert (works, but browsers will warn)
&lt;/h2&gt;

&lt;p&gt;This is quick, but you’ll typically see a browser warning unless you manually trust the cert/CA.&lt;/p&gt;

&lt;p&gt;Modern browsers require SAN, so use an OpenSSL config:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create &lt;code&gt;localhost.cnf&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[req]&lt;/span&gt;
&lt;span class="py"&gt;default_bits&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;2048&lt;/span&gt;
&lt;span class="py"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;no&lt;/span&gt;
&lt;span class="py"&gt;default_md&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;sha256&lt;/span&gt;
&lt;span class="py"&gt;x509_extensions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;v3_req&lt;/span&gt;
&lt;span class="py"&gt;distinguished_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;dn&lt;/span&gt;

&lt;span class="nn"&gt;[dn]&lt;/span&gt;
&lt;span class="py"&gt;CN&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;localhost&lt;/span&gt;

&lt;span class="nn"&gt;[v3_req]&lt;/span&gt;
&lt;span class="py"&gt;subjectAltName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;@alt_names&lt;/span&gt;

&lt;span class="nn"&gt;[alt_names]&lt;/span&gt;
&lt;span class="py"&gt;DNS.1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;localhost&lt;/span&gt;
&lt;span class="py"&gt;IP.1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;127.0.0.1&lt;/span&gt;
&lt;span class="py"&gt;IP.2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;::1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Generate cert + key:
&lt;/li&gt;
&lt;/ol&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;-nodes&lt;/span&gt; &lt;span class="nt"&gt;-days&lt;/span&gt; 825 &lt;span class="nt"&gt;-newkey&lt;/span&gt; rsa:2048 &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="nt"&gt;-config&lt;/span&gt; localhost.cnf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then point your server to &lt;code&gt;localhost.crt&lt;/code&gt; / &lt;code&gt;localhost.key&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you want no warnings, you must add trust manually (varies by OS/browser) or create a local CA and trust it (which is effectively what &lt;code&gt;mkcert&lt;/code&gt; automates).&lt;/p&gt;




&lt;p&gt;Posted on 02 January 2026&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;“Certificate not valid for this host”&lt;/strong&gt;: your cert is missing SAN entries for &lt;code&gt;localhost&lt;/code&gt; or &lt;code&gt;127.0.0.1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chrome/Edge caching/HSTS&lt;/strong&gt;: if you previously forced HTTPS or hit a bad cert, you may need to clear HSTS for &lt;code&gt;localhost&lt;/code&gt; (or use a different hostname like &lt;code&gt;myapp.local&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Firefox trust differs&lt;/strong&gt;: on some systems Firefox uses its own store; &lt;code&gt;mkcert -install&lt;/code&gt; usually handles this if the right NSS tooling is present.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Port 443 permissions&lt;/strong&gt;: on macOS/Linux, binding to 443 may require admin/root; use 8443 or a reverse proxy that listens on 443.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Containers&lt;/strong&gt;: if your server runs in Docker, mount the cert/key into the container and reference those paths; trust is still on the host/browser side.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Practical recommendation
&lt;/h2&gt;

&lt;p&gt;If your goal is simply “make &lt;code&gt;https://localhost/&lt;/code&gt; work cleanly in a browser,” use &lt;strong&gt;mkcert&lt;/strong&gt; and run your dev server on &lt;strong&gt;8443&lt;/strong&gt; (or use a reverse proxy to 443). It’s the fastest path to a trusted cert with correct SANs.&lt;/p&gt;

&lt;p&gt;If you tell me what stack you’re using (Node/Express, Vite, Next.js, Django, Nginx, IIS, Docker, etc.), I can give the exact config snippet for that specific server.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>php</category>
      <category>localhost</category>
      <category>ssl</category>
    </item>
  </channel>
</rss>
