<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>SSL on KnightLi Blog</title>
        <link>https://www.knightli.com/en/tags/ssl/</link>
        <description>Recent content in SSL on KnightLi Blog</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en</language>
        <lastBuildDate>Fri, 03 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://www.knightli.com/en/tags/ssl/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>Automatically Renew Let&#39;s Encrypt Certificates on Ubuntu (Certbot &#43; Nginx)</title>
        <link>https://www.knightli.com/en/2026/04/03/certbot-auto-renew-nginx/</link>
        <pubDate>Fri, 03 Apr 2026 00:00:00 +0000</pubDate>
        
        <guid>https://www.knightli.com/en/2026/04/03/certbot-auto-renew-nginx/</guid>
        <description>&lt;p&gt;Let&amp;rsquo;s Encrypt certificates are valid for only 90 days, so production sites should always enable automatic renewal to avoid HTTPS downtime.&lt;/p&gt;
&lt;p&gt;If you already issued the certificate with Certbot, there are usually two things left:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Configure a scheduled renewal task&lt;/li&gt;
&lt;li&gt;Verify the renewal workflow actually works&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;first-check-whether-certbot-already-created-a-scheduler&#34;&gt;First, Check Whether Certbot Already Created a Scheduler
&lt;/h2&gt;&lt;p&gt;Depending on your distro, Certbot may already install a scheduler (for example, a &lt;code&gt;systemd timer&lt;/code&gt; or &lt;code&gt;/etc/cron.d/certbot&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;You can check with:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;systemctl list-timers &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; grep certbot
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;If a valid timer already exists, you usually do not need an extra crontab entry.&lt;/p&gt;
&lt;h2 id=&#34;add-a-crontab-job-manually-recommended-example&#34;&gt;Add a Crontab Job Manually (Recommended Example)
&lt;/h2&gt;&lt;p&gt;If you prefer managing renewal explicitly, edit root crontab:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo crontab -e
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Add this line (runs daily at 03:00):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;0 3 * * * certbot renew --pre-hook &amp;#34;systemctl stop nginx&amp;#34; --post-hook &amp;#34;systemctl start nginx&amp;#34; &amp;gt;&amp;gt; /tmp/certbot-renew.log 2&amp;gt;&amp;amp;1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;What it means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;0 3 * * *&lt;/code&gt;: run at 03:00 every day&lt;/li&gt;
&lt;li&gt;&lt;code&gt;certbot renew&lt;/code&gt;: renew certificates that are close to expiration&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--pre-hook&lt;/code&gt;: stop Nginx before renewal (common for standalone mode)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--post-hook&lt;/code&gt;: start Nginx after renewal&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;gt;&amp;gt; /tmp/certbot-renew.log 2&amp;gt;&amp;amp;1&lt;/code&gt;: append logs for troubleshooting&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;run-a-dry-test-before-relying-on-cron&#34;&gt;Run a Dry Test Before Relying on Cron
&lt;/h2&gt;&lt;p&gt;After adding the task, validate the full flow manually:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo certbot renew --dry-run
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;If dry-run succeeds, you can safely rely on the scheduled job.&lt;/p&gt;
&lt;h2 id=&#34;common-notes&#34;&gt;Common Notes
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;If you use the &lt;code&gt;webroot&lt;/code&gt; or &lt;code&gt;nginx&lt;/code&gt; plugin, you often do not need to stop Nginx. In many setups, reloading Nginx after renewal is enough:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;certbot renew --deploy-hook &lt;span class=&#34;s2&#34;&gt;&amp;#34;systemctl reload nginx&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;certbot renew&lt;/code&gt; only performs actual renewal near expiration, so running it daily is normal.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For long-term maintenance, consider writing logs to a persistent path such as &lt;code&gt;/var/log/letsencrypt/&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;summary&#34;&gt;Summary
&lt;/h2&gt;&lt;p&gt;Reliable certificate auto-renewal is not just about writing a command. The key is confirming the workflow can run end to end.&lt;/p&gt;
&lt;p&gt;A stable setup is usually just these three steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Check whether system-level scheduling already exists&lt;/li&gt;
&lt;li&gt;Add cron if needed and keep logs&lt;/li&gt;
&lt;li&gt;Validate once with &lt;code&gt;--dry-run&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
        </item>
        
    </channel>
</rss>
