<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Https on Krishnatejaswi S</title>
    <link>https://KTS-o7.github.io/bear/tags/https/</link>
    <description>Recent content in Https on Krishnatejaswi S</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <managingEditor>shentharkrishnatejaswi@gmail.com (Krishnatejaswi S)</managingEditor>
    <webMaster>shentharkrishnatejaswi@gmail.com (Krishnatejaswi S)</webMaster>
    <copyright>Krishnatejaswi S</copyright>
    <lastBuildDate>Sat, 27 Dec 2025 00:00:00 +0530</lastBuildDate>
    <atom:link href="https://KTS-o7.github.io/bear/tags/https/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>End-to-End HTTPS with Cloudflare Origin Certificates and Nginx</title>
      <link>https://KTS-o7.github.io/bear/infra/reverse_proxy/</link>
      <pubDate>Sat, 27 Dec 2025 00:00:00 +0530</pubDate><author>shentharkrishnatejaswi@gmail.com (Krishnatejaswi S)</author>
      <guid>https://KTS-o7.github.io/bear/infra/reverse_proxy/</guid>
      <description>&lt;p&gt;Setting up proper SSL/TLS for your web application can be confusing, especially when Cloudflare is in the picture. This guide walks through configuring end-to-end encryption from browser to origin server using Cloudflare Origin Certificates with an Nginx reverse proxy running in Docker.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h3 id=&#34;the-problem-cloudflare-521-errors&#34;&gt;The Problem: Cloudflare 521 Errors&lt;/h3&gt;&#xA;&lt;p&gt;If you&amp;rsquo;re running Nginx behind Cloudflare and seeing &lt;strong&gt;521 errors&lt;/strong&gt;, you likely have a mismatch between your Cloudflare SSL mode and what your origin server supports.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Setting up proper SSL/TLS for your web application can be confusing, especially when Cloudflare is in the picture. This guide walks through configuring end-to-end encryption from browser to origin server using Cloudflare Origin Certificates with an Nginx reverse proxy running in Docker.</p>
<hr>
<h3 id="the-problem-cloudflare-521-errors">The Problem: Cloudflare 521 Errors</h3>
<p>If you&rsquo;re running Nginx behind Cloudflare and seeing <strong>521 errors</strong>, you likely have a mismatch between your Cloudflare SSL mode and what your origin server supports.</p>
<p><strong>Common scenario:</strong></p>
<ul>
<li>Nginx is only listening on port 80 (HTTP)</li>
<li>Cloudflare SSL mode is set to &ldquo;Full&rdquo; or &ldquo;Full (Strict)&rdquo;</li>
<li>Cloudflare tries to connect to your origin on port 443</li>
<li>Origin doesn&rsquo;t respond → <strong>521 Error</strong></li>
</ul>
<p><a href="https://mermaid.live/edit#pako:eNplkE1qwzAQha9izNqOZUm2ZAdCnEJ3XXVVvBjLk1jEP0GSQ0Lw3avYpIvOaoZ5H_N4C8i2IshgaPlQ-5x2bum6i-_PvBp4NfmUjWaINofejGeySrXlpZnlZJy8zFfh6G3P_U3-YkCMt_JNJbzTZCz3r-bLdkZOlRQ8Dab3_JYvq9LJ67gHb0fHf7x3Xo9yPAz8ZthZKe8G7ozrLO_F5zd4M8xBBLJiO9qBMvDbsqaEgvYtFKAXr4MSattz1tBh0n4DJYxdw5mz2hicMujV-OfCEzIn-wZS0qTzFDLXrTfOlWdIf6D4AQP0bwI"><img src="https://mermaid.ink/img/pako:eNplkE1qwzAQha9izNqOZUm2ZAdCnEJ3XXVVvBjLk1jEP0GSQ0Lw3avYpIvOaoZ5H_N4C8i2IshgaPlQ-5x2bum6i-_PvBp4NfmUjWaINofejGeySrXlpZnlZJy8zFfh6G3P_U3-YkCMt_JNJbzTZCz3r-bLdkZOlRQ8Dab3_JYvq9LJ67gHb0fHf7x3Xo9yPAz8ZthZKe8G7ozrLO_F5zd4M8xBBLJiO9qBMvDbsqaEgvYtFKAXr4MSattz1tBh0n4DJYxdw5mz2hicMujV-OfCEzIn-wZS0qTzFDLXrTfOlWdIf6D4AQP0bwI?type=png" alt=""></a></p>
<hr>
<h3 id="understanding-cloudflare-ssltls-modes">Understanding Cloudflare SSL/TLS Modes</h3>
<p>Cloudflare offers four SSL modes. Understanding these is crucial:</p>
<table>
  <thead>
      <tr>
          <th>Mode</th>
          <th>Browser → Cloudflare</th>
          <th>Cloudflare → Origin</th>
          <th>Certificate Validation</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Off</strong></td>
          <td>HTTP</td>
          <td>HTTP</td>
          <td>None</td>
      </tr>
      <tr>
          <td><strong>Flexible</strong></td>
          <td>HTTPS ✅</td>
          <td>HTTP</td>
          <td>None</td>
      </tr>
      <tr>
          <td><strong>Full</strong></td>
          <td>HTTPS ✅</td>
          <td>HTTPS ✅</td>
          <td>Accepts any certificate</td>
      </tr>
      <tr>
          <td><strong>Full (Strict)</strong></td>
          <td>HTTPS ✅</td>
          <td>HTTPS ✅</td>
          <td>Validates certificate ✅</td>
      </tr>
  </tbody>
</table>
<p><strong>Key differences:</strong></p>
<ul>
<li>
<p><strong>Flexible</strong>: Quick fix if your origin can&rsquo;t do HTTPS, but traffic between Cloudflare and your server is unencrypted — vulnerable to MITM attacks on that leg.</p>
</li>
<li>
<p><strong>Full</strong>: Encrypts both legs, but accepts <em>any</em> certificate (even self-signed, expired, or attacker-provided). Provides encryption but not authentication.</p>
</li>
<li>
<p><strong>Full (Strict)</strong>: The gold standard. Cloudflare validates that your origin certificate is either:</p>
<ul>
<li>A valid public CA certificate</li>
<li>A Cloudflare Origin Certificate</li>
</ul>
<p>This prevents MITM attacks where an attacker presents a fake certificate.</p>
</li>
</ul>
<hr>
<h3 id="architecture-overview">Architecture Overview</h3>
<p>Our target architecture:</p>





<pre tabindex="0"><code>┌──────────────────────────────────────────────────────────────────────┐
│                          INTERNET                                    │
└──────────────────────────┬───────────────────────────────────────────┘
                           │ HTTPS (443)
                           ▼
┌──────────────────────────────────────────────────────────────────────┐
│                     CLOUDFLARE EDGE                                  │
│              (SSL Termination + CDN + WAF)                           │
└──────────────────────────┬───────────────────────────────────────────┘
                           │ HTTPS (443) - Origin Certificate
                           ▼
┌──────────────────────────────────────────────────────────────────────┐
│                    YOUR EC2 INSTANCE                                 │
│  ┌─────────────────────────────────────────────────────────────────┐ │
│  │                    DOCKER NETWORK                               │ │
│  │                                                                 │ │
│  │   ┌─────────────┐    HTTP    ┌─────────────────────────────┐    │ │
│  │   │   NGINX     │───────────▶│     APPLICATION CONTAINERS  │    │ │
│  │   │  (SSL/443)  │            │  - Frontend (Next.js:3000)  │    │ │
│  │   │  (80→443)   │            │  - Backend (FastAPI:8000)   │    │ │
│  │   └─────────────┘            │  - Cloud APIs               │    │ │
│  │         ▲                    └─────────────────────────────┘    │ │
│  │         │                                                       │ │
│  └─────────┼───────────────────────────────────────────────────────┘ │
│            │                                                         │
│     Ports 80, 443                                                    │
└──────────────────────────────────────────────────────────────────────┘</code></pre><p><strong>Traffic flow:</strong></p>
<ol>
<li>Browser connects to Cloudflare over HTTPS (public SSL certificate)</li>
<li>Cloudflare connects to your origin over HTTPS (Origin Certificate)</li>
<li>Nginx terminates SSL and proxies to backend containers over HTTP</li>
<li>Backend containers communicate on internal Docker network (isolated)</li>
</ol>
<hr>
<h3 id="prerequisites">Prerequisites</h3>
<p>Before starting, ensure you have:</p>
<ul>
<li>A domain proxied through Cloudflare (orange cloud enabled)</li>
<li>Docker and Docker Compose installed on your server</li>
<li>An existing Nginx configuration (we&rsquo;ll modify it)</li>
<li>Access to Cloudflare dashboard</li>
<li><strong>Firewall Rules</strong>: Ensure ports 80 and 443 are open in your cloud provider&rsquo;s firewall (AWS Security Group, etc.)</li>
</ul>
<hr>
<h3 id="step-1-generate-a-cloudflare-origin-certificate">Step 1: Generate a Cloudflare Origin Certificate</h3>
<p>Cloudflare Origin Certificates are free, trusted only by Cloudflare, and can be valid for up to 15 years.</p>
<ol>
<li>Log into <a href="https://dash.cloudflare.com">Cloudflare Dashboard</a></li>
<li>Select your domain</li>
<li>Navigate to <strong>SSL/TLS</strong> → <strong>Origin Server</strong></li>
<li>Click <strong>Create Certificate</strong></li>
<li>Configure:
<ul>
<li><strong>Private key type</strong>: RSA (2048)</li>
<li><strong>Hostnames</strong>: <code>*.yourdomain.com, yourdomain.com</code></li>
<li><strong>Validity</strong>: 15 years (recommended)</li>
</ul>
</li>
<li>Click <strong>Create</strong></li>
</ol>
<p><strong>⚠️ IMPORTANT</strong>: Copy both the <strong>Origin Certificate</strong> (PEM) and <strong>Private Key</strong> immediately. The private key is shown only once!</p>
<p>Save them to files:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Create SSL directory</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">mkdir -p ssl
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="c1"># Paste the certificate</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">cat &gt; ssl/origin.pem <span class="s">&lt;&lt; &#39;EOF&#39;
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="s">-----BEGIN CERTIFICATE-----
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="s">YOUR_CERTIFICATE_HERE
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="s">-----END CERTIFICATE-----
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="s">EOF</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="c1"># Paste the private key</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">cat &gt; ssl/origin.key <span class="s">&lt;&lt; &#39;EOF&#39;
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="s">-----BEGIN PRIVATE KEY-----
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="s">YOUR_PRIVATE_KEY_HERE
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="s">-----END PRIVATE KEY-----
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="s">EOF</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl">
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="c1"># Set secure permissions</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">chmod <span class="m">600</span> ssl/origin.key
</span></span><span class="line"><span class="ln">20</span><span class="cl">chmod <span class="m">644</span> ssl/origin.pem</span></span></code></pre></div><p>Add to <code>.gitignore</code> to never commit these:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;ssl/*&#34;</span> &gt;&gt; .gitignore</span></span></code></pre></div><hr>
<h3 id="step-2-configure-nginx-for-https">Step 2: Configure Nginx for HTTPS</h3>
<p>Here&rsquo;s a complete Nginx configuration with SSL, security headers, rate limiting, and reverse proxy setup.</p>
<p><strong><code>nginx/nginx-main.conf</code></strong> - Main HTTP block configuration:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># Main nginx configuration with security settings
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">user</span> <span class="s">nginx</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">worker_processes</span> <span class="s">auto</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">error_log</span> <span class="s">/var/log/nginx/error.log</span> <span class="s">warn</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">pid</span> <span class="s">/var/run/nginx.pid</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl">
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="k">events</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">    <span class="kn">worker_connections</span> <span class="mi">1024</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">    <span class="kn">use</span> <span class="s">epoll</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="kn">multi_accept</span> <span class="no">on</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="k">http</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">    <span class="kn">include</span> <span class="s">/etc/nginx/mime.types</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">    <span class="kn">default_type</span> <span class="s">application/octet-stream</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl">
</span></span><span class="line"><span class="ln">17</span><span class="cl">    <span class="c1"># Logging format with security information
</span></span></span><span class="line"><span class="ln">18</span><span class="cl">    <span class="kn">log_format</span> <span class="s">security</span> <span class="s">&#39;</span><span class="nv">$real_ip</span> <span class="s">-</span> <span class="nv">$remote_user</span> <span class="s">[</span><span class="nv">$time_local]</span> <span class="s">&#34;</span><span class="nv">$request&#34;</span> <span class="s">&#39;</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">                       <span class="s">&#39;</span><span class="nv">$status</span> <span class="nv">$body_bytes_sent</span> <span class="s">&#34;</span><span class="nv">$http_referer&#34;</span> <span class="s">&#39;</span>
</span></span><span class="line"><span class="ln">20</span><span class="cl">                       <span class="s">&#39;&#34;</span><span class="nv">$http_user_agent&#34;</span> <span class="s">&#34;</span><span class="nv">$http_x_forwarded_for&#34;</span> <span class="s">&#39;</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl">                       <span class="s">&#39;rt=</span><span class="nv">$request_time</span> <span class="s">uct=&#34;</span><span class="nv">$upstream_connect_time&#34;</span> <span class="s">&#39;</span>
</span></span><span class="line"><span class="ln">22</span><span class="cl">                       <span class="s">&#39;uht=&#34;</span><span class="nv">$upstream_header_time&#34;</span> <span class="s">urt=&#34;</span><span class="nv">$upstream_response_time&#34;&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">23</span><span class="cl">
</span></span><span class="line"><span class="ln">24</span><span class="cl">    <span class="kn">access_log</span> <span class="s">/var/log/nginx/access.log</span> <span class="s">security</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">25</span><span class="cl">
</span></span><span class="line"><span class="ln">26</span><span class="cl">    <span class="c1"># Pro Tip: use $real_ip in log_format so you see actual visitor IPs, not Cloudflare&#39;s.
</span></span></span><span class="line"><span class="ln">27</span><span class="cl">
</span></span><span class="line"><span class="ln">28</span><span class="cl">    <span class="c1"># Basic settings
</span></span></span><span class="line"><span class="ln">29</span><span class="cl">    <span class="kn">sendfile</span> <span class="no">on</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">30</span><span class="cl">    <span class="kn">tcp_nopush</span> <span class="no">on</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">31</span><span class="cl">    <span class="kn">tcp_nodelay</span> <span class="no">on</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">32</span><span class="cl">    <span class="kn">keepalive_timeout</span> <span class="mi">65</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">33</span><span class="cl">    <span class="kn">types_hash_max_size</span> <span class="mi">2048</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">34</span><span class="cl">    <span class="kn">server_tokens</span> <span class="no">off</span><span class="p">;</span>  <span class="c1"># Hide nginx version
</span></span></span><span class="line"><span class="ln">35</span><span class="cl">
</span></span><span class="line"><span class="ln">36</span><span class="cl">    <span class="c1"># Gzip compression
</span></span></span><span class="line"><span class="ln">37</span><span class="cl">    <span class="kn">gzip</span> <span class="no">on</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">38</span><span class="cl">    <span class="kn">gzip_vary</span> <span class="no">on</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">39</span><span class="cl">    <span class="kn">gzip_proxied</span> <span class="s">any</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">40</span><span class="cl">    <span class="kn">gzip_comp_level</span> <span class="mi">6</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">41</span><span class="cl">    <span class="kn">gzip_types</span> <span class="s">text/plain</span> <span class="s">text/css</span> <span class="s">text/xml</span> <span class="s">text/javascript</span> 
</span></span><span class="line"><span class="ln">42</span><span class="cl">               <span class="s">application/json</span> <span class="s">application/javascript</span> <span class="s">application/xml+rss</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">43</span><span class="cl">
</span></span><span class="line"><span class="ln">44</span><span class="cl">    <span class="c1"># Rate limiting zones
</span></span></span><span class="line"><span class="ln">45</span><span class="cl">    <span class="kn">limit_req_zone</span> <span class="nv">$binary_remote_addr</span> <span class="s">zone=general_limit:10m</span> <span class="s">rate=50r/s</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">46</span><span class="cl">    <span class="kn">limit_req_zone</span> <span class="nv">$binary_remote_addr</span> <span class="s">zone=api_limit:10m</span> <span class="s">rate=30r/s</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">47</span><span class="cl">    <span class="kn">limit_req_zone</span> <span class="nv">$binary_remote_addr</span> <span class="s">zone=burst_limit:10m</span> <span class="s">rate=200r/m</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">48</span><span class="cl">    <span class="kn">limit_conn_zone</span> <span class="nv">$binary_remote_addr</span> <span class="s">zone=conn_limit:10m</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">49</span><span class="cl">
</span></span><span class="line"><span class="ln">50</span><span class="cl">    <span class="kn">include</span> <span class="s">/etc/nginx/conf.d/*.conf</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><strong><code>nginx/nginx.conf</code></strong> - Server block configuration with SSL:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="ln">  1</span><span class="cl"><span class="c1"># Cloudflare IP handling - extract real client IP
</span></span></span><span class="line"><span class="ln">  2</span><span class="cl"><span class="k">map</span> <span class="nv">$http_cf_connecting_ip</span> <span class="nv">$real_ip</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">  3</span><span class="cl">    <span class="kn">&#34;&#34;</span> <span class="nv">$remote_addr</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">  4</span><span class="cl">    <span class="kn">default</span> <span class="nv">$http_cf_connecting_ip</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">  5</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln">  6</span><span class="cl">
</span></span><span class="line"><span class="ln">  7</span><span class="cl"><span class="c1"># Cloudflare protocol handling
</span></span></span><span class="line"><span class="ln">  8</span><span class="cl"><span class="k">map</span> <span class="nv">$http_x_forwarded_proto</span> <span class="nv">$forwarded_proto</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">  9</span><span class="cl">    <span class="kn">&#34;&#34;</span> <span class="nv">$scheme</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 10</span><span class="cl">    <span class="kn">default</span> <span class="nv">$http_x_forwarded_proto</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 11</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 12</span><span class="cl">
</span></span><span class="line"><span class="ln"> 13</span><span class="cl"><span class="c1"># HTTP Server (Port 80) - Redirect to HTTPS
</span></span></span><span class="line"><span class="ln"> 14</span><span class="cl"><span class="k">server</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 15</span><span class="cl">    <span class="kn">listen</span> <span class="mi">80</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 16</span><span class="cl">    <span class="kn">server_name</span> <span class="s">_</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 17</span><span class="cl">
</span></span><span class="line"><span class="ln"> 18</span><span class="cl">    <span class="c1"># Health check (keep on port 80 for internal monitoring)
</span></span></span><span class="line"><span class="ln"> 19</span><span class="cl">    <span class="kn">location</span> <span class="s">/health</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 20</span><span class="cl">        <span class="kn">access_log</span> <span class="no">off</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 21</span><span class="cl">        <span class="kn">return</span> <span class="mi">200</span> <span class="s">&#34;healthy\n&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 22</span><span class="cl">        <span class="kn">add_header</span> <span class="s">Content-Type</span> <span class="s">text/plain</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 23</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 24</span><span class="cl">
</span></span><span class="line"><span class="ln"> 25</span><span class="cl">    <span class="c1"># Redirect all other traffic to HTTPS
</span></span></span><span class="line"><span class="ln"> 26</span><span class="cl">    <span class="kn">location</span> <span class="s">/</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 27</span><span class="cl">        <span class="kn">return</span> <span class="mi">301</span> <span class="s">https://</span><span class="nv">$host$request_uri</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 28</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 29</span><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="ln"> 30</span><span class="cl">
</span></span><span class="line"><span class="ln"> 31</span><span class="cl"><span class="c1"># HTTPS Server (Port 443)
</span></span></span><span class="line"><span class="ln"> 32</span><span class="cl"><span class="k">server</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 33</span><span class="cl">    <span class="kn">listen</span> <span class="mi">443</span> <span class="s">ssl</span> <span class="s">http2</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 34</span><span class="cl">    <span class="kn">server_name</span> <span class="s">_</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 35</span><span class="cl">
</span></span><span class="line"><span class="ln"> 36</span><span class="cl">    <span class="c1"># SSL Configuration - Cloudflare Origin Certificate
</span></span></span><span class="line"><span class="ln"> 37</span><span class="cl">    <span class="kn">ssl_certificate</span> <span class="s">/etc/nginx/ssl/origin.pem</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 38</span><span class="cl">    <span class="kn">ssl_certificate_key</span> <span class="s">/etc/nginx/ssl/origin.key</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 39</span><span class="cl">    
</span></span><span class="line"><span class="ln"> 40</span><span class="cl">    <span class="c1"># Modern SSL settings
</span></span></span><span class="line"><span class="ln"> 41</span><span class="cl">    <span class="kn">ssl_protocols</span> <span class="s">TLSv1.2</span> <span class="s">TLSv1.3</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 42</span><span class="cl">    <span class="kn">ssl_ciphers</span> <span class="s">ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 43</span><span class="cl">    <span class="kn">ssl_prefer_server_ciphers</span> <span class="no">off</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 44</span><span class="cl">    <span class="kn">ssl_session_timeout</span> <span class="s">1d</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 45</span><span class="cl">    <span class="kn">ssl_session_cache</span> <span class="s">shared:SSL:10m</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 46</span><span class="cl">    <span class="kn">ssl_session_tickets</span> <span class="no">off</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 47</span><span class="cl">
</span></span><span class="line"><span class="ln"> 48</span><span class="cl">    <span class="c1"># Connection and request limits
</span></span></span><span class="line"><span class="ln"> 49</span><span class="cl">    <span class="kn">limit_conn</span> <span class="s">conn_limit</span> <span class="mi">100</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 50</span><span class="cl">    <span class="kn">client_max_body_size</span> <span class="s">50M</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 51</span><span class="cl">    <span class="kn">client_body_buffer_size</span> <span class="s">4M</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 52</span><span class="cl">
</span></span><span class="line"><span class="ln"> 53</span><span class="cl">    <span class="c1"># Timeout limits (prevent slowloris attacks)
</span></span></span><span class="line"><span class="ln"> 54</span><span class="cl">    <span class="kn">client_body_timeout</span> <span class="s">10s</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 55</span><span class="cl">    <span class="kn">client_header_timeout</span> <span class="s">10s</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 56</span><span class="cl">    <span class="kn">send_timeout</span> <span class="s">10s</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 57</span><span class="cl">
</span></span><span class="line"><span class="ln"> 58</span><span class="cl">    <span class="c1"># Security headers
</span></span></span><span class="line"><span class="ln"> 59</span><span class="cl">    <span class="kn">add_header</span> <span class="s">X-Frame-Options</span> <span class="s">&#34;SAMEORIGIN&#34;</span> <span class="s">always</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 60</span><span class="cl">    <span class="kn">add_header</span> <span class="s">X-Content-Type-Options</span> <span class="s">&#34;nosniff&#34;</span> <span class="s">always</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 61</span><span class="cl">    <span class="kn">add_header</span> <span class="s">X-XSS-Protection</span> <span class="s">&#34;1</span><span class="p">;</span> <span class="kn">mode=block&#34;</span> <span class="s">always</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 62</span><span class="cl">    <span class="kn">add_header</span> <span class="s">Referrer-Policy</span> <span class="s">&#34;strict-origin-when-cross-origin&#34;</span> <span class="s">always</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 63</span><span class="cl">    <span class="kn">add_header</span> <span class="s">Permissions-Policy</span> <span class="s">&#34;geolocation=(),</span> <span class="s">microphone=(),</span> <span class="s">camera=()&#34;</span> <span class="s">always</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 64</span><span class="cl">    <span class="kn">add_header</span> <span class="s">Content-Security-Policy</span> <span class="s">&#34;default-src</span> <span class="s">&#39;self&#39;</span><span class="p">;</span> <span class="kn">script-src</span> <span class="s">&#39;self&#39;</span> <span class="s">&#39;unsafe-inline&#39;</span> <span class="s">&#39;unsafe-eval&#39;</span><span class="p">;</span> <span class="kn">style-src</span> <span class="s">&#39;self&#39;</span> <span class="s">&#39;unsafe-inline&#39;</span><span class="p">;</span> <span class="kn">img-src</span> <span class="s">&#39;self&#39;</span> <span class="s">data:</span> <span class="s">https:</span><span class="p">;</span> <span class="kn">font-src</span> <span class="s">&#39;self&#39;</span> <span class="s">data:</span><span class="p">;</span> <span class="kn">connect-src</span> <span class="s">&#39;self&#39;</span> <span class="s">https:</span><span class="p">;</span> <span class="kn">frame-src</span> <span class="s">&#39;self&#39;</span><span class="p">;</span><span class="kn">&#34;</span> <span class="s">always</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 65</span><span class="cl">
</span></span><span class="line"><span class="ln"> 66</span><span class="cl">    <span class="c1"># Frontend - Next.js
</span></span></span><span class="line"><span class="ln"> 67</span><span class="cl">    <span class="kn">location</span> <span class="s">/</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 68</span><span class="cl">        <span class="kn">limit_req</span> <span class="s">zone=general_limit</span> <span class="s">burst=100</span> <span class="s">nodelay</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 69</span><span class="cl">        
</span></span><span class="line"><span class="ln"> 70</span><span class="cl">        <span class="kn">proxy_pass</span> <span class="s">http://frontend:3000</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 71</span><span class="cl">        <span class="kn">proxy_http_version</span> <span class="mi">1</span><span class="s">.1</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 72</span><span class="cl">        <span class="kn">proxy_set_header</span> <span class="s">Upgrade</span> <span class="nv">$http_upgrade</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 73</span><span class="cl">        <span class="kn">proxy_set_header</span> <span class="s">Connection</span> <span class="s">&#39;upgrade&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 74</span><span class="cl">        <span class="kn">proxy_set_header</span> <span class="s">Host</span> <span class="nv">$host</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 75</span><span class="cl">        <span class="kn">proxy_set_header</span> <span class="s">X-Real-IP</span> <span class="nv">$real_ip</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 76</span><span class="cl">        <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-For</span> <span class="nv">$real_ip</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 77</span><span class="cl">        <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-Proto</span> <span class="nv">$forwarded_proto</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 78</span><span class="cl">        <span class="kn">proxy_cache_bypass</span> <span class="nv">$http_upgrade</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 79</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 80</span><span class="cl">
</span></span><span class="line"><span class="ln"> 81</span><span class="cl">    <span class="c1"># Backend API
</span></span></span><span class="line"><span class="ln"> 82</span><span class="cl">    <span class="kn">location</span> <span class="s">/api</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln"> 83</span><span class="cl">        <span class="kn">limit_req</span> <span class="s">zone=api_limit</span> <span class="s">burst=50</span> <span class="s">nodelay</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 84</span><span class="cl">        
</span></span><span class="line"><span class="ln"> 85</span><span class="cl">        <span class="kn">proxy_pass</span> <span class="s">http://backend:8000</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 86</span><span class="cl">        <span class="kn">proxy_http_version</span> <span class="mi">1</span><span class="s">.1</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 87</span><span class="cl">        <span class="kn">proxy_set_header</span> <span class="s">Host</span> <span class="nv">$host</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 88</span><span class="cl">        <span class="kn">proxy_set_header</span> <span class="s">X-Real-IP</span> <span class="nv">$real_ip</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 89</span><span class="cl">        <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-For</span> <span class="nv">$real_ip</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 90</span><span class="cl">        <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-Proto</span> <span class="nv">$forwarded_proto</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 91</span><span class="cl">        
</span></span><span class="line"><span class="ln"> 92</span><span class="cl">        <span class="c1"># Increased timeouts for long-running requests
</span></span></span><span class="line"><span class="ln"> 93</span><span class="cl">        <span class="kn">proxy_connect_timeout</span> <span class="s">300s</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 94</span><span class="cl">        <span class="kn">proxy_send_timeout</span> <span class="s">300s</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 95</span><span class="cl">        <span class="kn">proxy_read_timeout</span> <span class="s">300s</span><span class="p">;</span>
</span></span><span class="line"><span class="ln"> 96</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln"> 97</span><span class="cl">
</span></span><span class="line"><span class="ln"> 98</span><span class="cl">    <span class="c1"># Health check
</span></span></span><span class="line"><span class="ln"> 99</span><span class="cl">    <span class="kn">location</span> <span class="s">/health</span> <span class="p">{</span>
</span></span><span class="line"><span class="ln">100</span><span class="cl">        <span class="kn">access_log</span> <span class="no">off</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">101</span><span class="cl">        <span class="kn">return</span> <span class="mi">200</span> <span class="s">&#34;healthy\n&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">102</span><span class="cl">        <span class="kn">add_header</span> <span class="s">Content-Type</span> <span class="s">text/plain</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">103</span><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="ln">104</span><span class="cl"><span class="p">}</span></span></span></code></pre></div><blockquote>
<p><strong>Docker DNS Caveat</strong>: Nginx caches DNS records at startup. If your backend container restarts and gets a new IP, Nginx might start returning 502 errors. To prevent this, use Docker&rsquo;s internal resolver <code>resolver 127.0.0.11 valid=30s;</code> and variables for <code>proxy_pass</code> targets.</p>
</blockquote>
<hr>
<h3 id="step-3-update-dockerfile">Step 3: Update Dockerfile</h3>
<p>Update your Nginx Dockerfile to expose both ports:</p>
<p><strong><code>nginx/Dockerfile</code></strong>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dockerfile" data-lang="dockerfile"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">nginx:alpine</span><span class="err">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="c"># Copy configurations</span><span class="err">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">COPY</span> nginx-main.conf /etc/nginx/nginx.conf<span class="err">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">COPY</span> nginx.conf /etc/nginx/conf.d/default.conf<span class="err">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="c"># Expose HTTP and HTTPS ports</span><span class="err">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="k">EXPOSE</span><span class="w"> </span><span class="s">80</span> <span class="m">443</span><span class="err">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">CMD</span> <span class="p">[</span><span class="s2">&#34;nginx&#34;</span><span class="p">,</span> <span class="s2">&#34;-g&#34;</span><span class="p">,</span> <span class="s2">&#34;daemon off;&#34;</span><span class="p">]</span></span></span></code></pre></div><hr>
<h3 id="step-4-update-docker-compose">Step 4: Update Docker Compose</h3>
<p>Mount the SSL certificates and expose port 443:</p>
<p><strong><code>docker-compose.yml</code></strong> (nginx service section):</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w">  </span><span class="nt">nginx</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">    </span><span class="nt">build</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">      </span><span class="nt">context</span><span class="p">:</span><span class="w"> </span><span class="l">./nginx</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">      </span><span class="nt">dockerfile</span><span class="p">:</span><span class="w"> </span><span class="l">Dockerfile</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">my-nginx</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">      </span>- <span class="s2">&#34;80:80&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">      </span>- <span class="s2">&#34;443:443&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">      </span>- <span class="l">frontend</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w">      </span>- <span class="l">backend</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">      </span>- <span class="l">app-network</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w">      </span>- <span class="l">./ssl:/etc/nginx/ssl:ro </span><span class="w"> </span><span class="c"># Read-only mount</span><span class="w">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="w">    </span><span class="nt">healthcheck</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="w">      </span><span class="nt">test</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;CMD&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;curl&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;-f&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;http://localhost/health&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w">      </span><span class="nt">interval</span><span class="p">:</span><span class="w"> </span><span class="l">30s</span><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="w">      </span><span class="nt">timeout</span><span class="p">:</span><span class="w"> </span><span class="l">10s</span><span class="w">
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="w">      </span><span class="nt">retries</span><span class="p">:</span><span class="w"> </span><span class="m">3</span><span class="w">
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="w">  </span><span class="nt">app-network</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="w">    </span><span class="nt">driver</span><span class="p">:</span><span class="w"> </span><span class="l">bridge</span></span></span></code></pre></div><hr>
<h3 id="step-5-deploy">Step 5: Deploy</h3>
<p>Rebuild and deploy only the nginx container (zero downtime for other services):</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># Build the new nginx image</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">docker compose build nginx
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># Recreate nginx container without touching dependencies</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">docker compose up -d --no-deps nginx
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="c1"># Verify nginx is running</span>
</span></span><span class="line"><span class="ln">8</span><span class="cl">docker compose logs nginx --tail<span class="o">=</span><span class="m">20</span></span></span></code></pre></div><p>If you have a deploy script like <code>prod.sh</code>, you might need:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># If container name conflicts exist</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">docker stop my-nginx <span class="o">&amp;&amp;</span> docker rm my-nginx
</span></span><span class="line"><span class="ln">3</span><span class="cl">./prod.sh -d nginx</span></span></code></pre></div><hr>
<h3 id="step-6-enable-full-strict-mode-in-cloudflare">Step 6: Enable Full (Strict) Mode in Cloudflare</h3>
<p>Now that your origin supports HTTPS with a valid Origin Certificate:</p>
<ol>
<li>Go to <strong>Cloudflare Dashboard</strong> → <strong>SSL/TLS</strong> → <strong>Overview</strong></li>
<li>Change encryption mode from <code>Flexible</code> or <code>Full</code> to <strong><code>Full (Strict)</code></strong></li>
</ol>
<p>This change takes effect immediately.</p>
<hr>
<h3 id="step-7-verify">Step 7: Verify</h3>
<p>Test the connection:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl">curl -I https://yourdomain.com</span></span></code></pre></div><p>Expected output shows HTTP/2 200 with Cloudflare and security headers:</p>





<pre tabindex="0"><code>HTTP/2 200 
server: cloudflare
x-frame-options: SAMEORIGIN
x-content-type-options: nosniff
content-security-policy: default-src &#39;self&#39;...
cf-ray: xxxxxxxx-XXX</code></pre><hr>
<h3 id="troubleshooting">Troubleshooting</h3>
<h4 id="521-error-web-server-is-down">521 Error (Web server is down)</h4>
<ul>
<li>Origin not listening on port 443</li>
<li>Firewall blocking port 443</li>
<li>Nginx failed to start (check certificate paths)</li>
</ul>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># Check nginx logs</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">docker compose logs nginx
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># Verify nginx config syntax</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">docker compose <span class="nb">exec</span> nginx nginx -t</span></span></code></pre></div><h4 id="525-error-ssl-handshake-failed">525 Error (SSL handshake failed)</h4>
<ul>
<li>Certificate/key mismatch</li>
<li>Invalid certificate format</li>
<li>Wrong file permissions</li>
</ul>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># Verify certificate and key match</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">openssl x509 -noout -modulus -in ssl/origin.pem <span class="p">|</span> openssl md5
</span></span><span class="line"><span class="ln">3</span><span class="cl">openssl rsa -noout -modulus -in ssl/origin.key <span class="p">|</span> openssl md5
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># Both should output the same MD5 hash</span></span></span></code></pre></div><h4 id="526-error-invalid-ssl-certificate">526 Error (Invalid SSL certificate)</h4>
<ul>
<li>Only occurs in Full (Strict) mode</li>
<li>Origin certificate not trusted by Cloudflare</li>
<li>Certificate expired or for wrong hostname</li>
</ul>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># Check certificate details</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">openssl x509 -in ssl/origin.pem -text -noout <span class="p">|</span> grep -A2 <span class="s2">&#34;Subject:&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">openssl x509 -in ssl/origin.pem -text -noout <span class="p">|</span> grep -A2 <span class="s2">&#34;Validity&#34;</span></span></span></code></pre></div><hr>
<h3 id="security-considerations">Security Considerations</h3>
<h4 id="what-this-setup-protects-against">What This Setup Protects Against</h4>
<table>
  <thead>
      <tr>
          <th>Attack</th>
          <th>Protected?</th>
          <th>How</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>MITM (Browser → Cloudflare)</td>
          <td>✅</td>
          <td>Public SSL certificate</td>
      </tr>
      <tr>
          <td>MITM (Cloudflare → Origin)</td>
          <td>✅</td>
          <td>Origin Certificate + Full (Strict)</td>
      </tr>
      <tr>
          <td>DDoS</td>
          <td>✅</td>
          <td>Cloudflare absorbs attack traffic</td>
      </tr>
      <tr>
          <td>SSL Downgrade</td>
          <td>✅</td>
          <td>HTTPS redirect + HSTS (via Cloudflare)</td>
      </tr>
  </tbody>
</table>
<h4 id="additional-hardening-optional">Additional Hardening (Optional)</h4>
<ol>
<li>
<p><strong>Authenticated Origin Pulls</strong>: Mutual TLS where Cloudflare presents a client certificate</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">ssl_client_certificate</span> <span class="s">/etc/nginx/ssl/cloudflare-origin-pull.pem</span><span class="p">;</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="k">ssl_verify_client</span> <span class="no">on</span><span class="p">;</span></span></span></code></pre></div></li>
<li>
<p><strong>Restrict Origin to Cloudflare IPs Only</strong>: Configure AWS Security Groups or iptables to only accept connections from <a href="https://www.cloudflare.com/ips/">Cloudflare IP ranges</a></p>
</li>
<li>
<p><strong>Enable HSTS in Cloudflare</strong>: SSL/TLS → Edge Certificates → Always Use HTTPS + HSTS</p>
</li>
</ol>
<hr>
<h3 id="quick-reference">Quick Reference</h3>
<table>
  <thead>
      <tr>
          <th>Task</th>
          <th>Command</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Create SSL directory</td>
          <td><code>mkdir -p ssl</code></td>
      </tr>
      <tr>
          <td>Set key permissions</td>
          <td><code>chmod 600 ssl/origin.key</code></td>
      </tr>
      <tr>
          <td>Build nginx</td>
          <td><code>docker compose build nginx</code></td>
      </tr>
      <tr>
          <td>Deploy nginx only</td>
          <td><code>docker compose up -d --no-deps nginx</code></td>
      </tr>
      <tr>
          <td>Check nginx logs</td>
          <td><code>docker compose logs nginx --tail=50</code></td>
      </tr>
      <tr>
          <td>Test nginx config</td>
          <td><code>docker compose exec nginx nginx -t</code></td>
      </tr>
      <tr>
          <td>Test HTTPS</td>
          <td><code>curl -I https://yourdomain.com</code></td>
      </tr>
  </tbody>
</table>
<hr>
<h3 id="trade-offs-and-limitations">Trade-offs and Limitations</h3>
<p><strong>1. Vendor Lock-in</strong>:
Cloudflare Origin Certificates are not trusted by major browsers, only by Cloudflare. This means you cannot bypass Cloudflare and connect directly to your origin server IP in a browser without getting severe security warnings. If you ever move away from Cloudflare, you will need to replace these certificates with standard ones (e.g., Let&rsquo;s Encrypt).</p>
<p><strong>2. Local Development</strong>:
Since these certificates are for specific public domains, you can&rsquo;t easily use them for <code>localhost</code> development. A common pattern is to have a separate <code>docker-compose.override.yml</code> for local development that overrides the Nginx config to listen on HTTP only or uses mkcert for locally trusted self-signed certificates.</p>
<hr>
<h3 id="conclusion">Conclusion</h3>
<p>With this setup, you now have:</p>
<p>✅ <strong>End-to-end encryption</strong> from browser to origin<br>
✅ <strong>MITM protection</strong> on all network segments<br>
✅ <strong>Zero-cost SSL</strong> with 15-year Cloudflare Origin Certificate<br>
✅ <strong>Rate limiting</strong> and security headers<br>
✅ <strong>Docker-based deployment</strong> for easy management</p>
<p>The key insight is that <strong>Full (Strict) mode</strong> is what provides actual security. Without it, Cloudflare will accept any certificate from your origin, including one presented by an attacker performing a MITM attack.</p>
<hr>
<h3 id="further-reading">Further Reading</h3>
<ul>
<li><a href="https://developers.cloudflare.com/ssl/origin-configuration/origin-ca/">Cloudflare Origin CA certificates</a></li>
<li><a href="https://developers.cloudflare.com/ssl/origin-configuration/ssl-modes/">Cloudflare SSL/TLS encryption modes</a></li>
<li><a href="https://developers.cloudflare.com/ssl/origin-configuration/authenticated-origin-pull/">Authenticated Origin Pulls</a></li>
<li><a href="https://ssl-config.mozilla.org/">Nginx SSL configuration best practices</a></li>
</ul>
]]></content:encoded>
    </item>
  </channel>
</rss>
