<?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: Sospeter Mong'are</title>
    <description>The latest articles on DEV Community by Sospeter Mong'are (@msnmongare).</description>
    <link>https://dev.to/msnmongare</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F117091%2F89122cee-2645-481e-b979-f96819dc9d1b.jpeg</url>
      <title>DEV Community: Sospeter Mong'are</title>
      <link>https://dev.to/msnmongare</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/msnmongare"/>
    <language>en</language>
    <item>
      <title>Getting Started with Dagster: A Beginner's Guide</title>
      <dc:creator>Sospeter Mong'are</dc:creator>
      <pubDate>Wed, 09 Sep 2026 14:17:58 +0000</pubDate>
      <link>https://dev.to/msnmongare/getting-started-with-dagster-a-beginners-guide-21f</link>
      <guid>https://dev.to/msnmongare/getting-started-with-dagster-a-beginners-guide-21f</guid>
      <description>&lt;p&gt;Welcome! Let's create a simple Dagster project step by step. 🎉&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Scaffold a New Dagster Project
&lt;/h2&gt;

&lt;p&gt;Open your WSL terminal and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;create-dagster
create-dagster project my-first-dagster-project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then navigate into your project:&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;cd &lt;/span&gt;my-first-dagster-project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://docs.dagster.io/dagster-basics-tutorial/projects" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Set Up Your Virtual Environment
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--editable&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://docs.dagster.io/dagster-basics-tutorial/projects" rel="noopener noreferrer"&gt;2&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Your Project Structure
&lt;/h2&gt;

&lt;p&gt;Your project should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── pyproject.toml
├── README.md
├── src
│   └── my_first_dagster_project
│       ├── __init__.py
│       ├── definitions.py
│       └── defs
│           └── __init__.py
└── tests
    └── __init__.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://docs.dagster.io/dagster-basics-tutorial/projects" rel="noopener noreferrer"&gt;3&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Create Your First Asset
&lt;/h2&gt;

&lt;p&gt;Inside &lt;code&gt;src/my_first_dagster_project/defs/&lt;/code&gt;, create a file called &lt;code&gt;assets.py&lt;/code&gt; and add this simple code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dagster&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;dg&lt;/span&gt;

&lt;span class="nd"&gt;@dg.asset&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;my_first_asset&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello from my first Dagster asset!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nd"&gt;@dg.asset&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;my_second_asset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_first_asset&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_first_asset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The sum is: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="si"&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;return&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 5: Register Your Assets in &lt;code&gt;definitions.py&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Open &lt;code&gt;src/my_first_dagster_project/definitions.py&lt;/code&gt; and update it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dagster&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;dg&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;my_first_dagster_project.defs.assets&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;my_first_asset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;my_second_asset&lt;/span&gt;

&lt;span class="n"&gt;defs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Definitions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;assets&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;my_first_asset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;my_second_asset&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;
  
  
  Step 6: Launch the Dagster UI
&lt;/h2&gt;

&lt;p&gt;Run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dg dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open your browser and go to:&lt;br&gt;
👉 &lt;strong&gt;&lt;a href="http://127.0.0.1:3000" rel="noopener noreferrer"&gt;http://127.0.0.1:3000&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.dagster.io/dagster-basics-tutorial/projects" rel="noopener noreferrer"&gt;4&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 7: Materialize Your Assets
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;In the Dagster UI, click on &lt;strong&gt;Assets&lt;/strong&gt; in the top navigation&lt;/li&gt;
&lt;li&gt;You should see &lt;code&gt;my_first_asset&lt;/code&gt; and &lt;code&gt;my_second_asset&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Materialize All&lt;/strong&gt; to run them&lt;/li&gt;
&lt;li&gt;Watch them execute and check the logs! ✅&lt;/li&gt;
&lt;/ol&gt;


&lt;h2&gt;
  
  
  What Just Happened? 🤔
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;my_first_asset&lt;/code&gt;&lt;/strong&gt; runs first and returns a list of numbers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;my_second_asset&lt;/code&gt;&lt;/strong&gt; depends on &lt;code&gt;my_first_asset&lt;/code&gt; and calculates the sum&lt;/li&gt;
&lt;li&gt;Dagster automatically figures out the &lt;strong&gt;execution order&lt;/strong&gt; based on dependencies!&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;&lt;strong&gt;Question&lt;/strong&gt;: Should I be running this everytime, Even though I installed Dagster?&lt;/p&gt;

&lt;p&gt;Great question! &lt;strong&gt;No, you don't need to run those commands every time.&lt;/strong&gt; Here's what each command does and when you need it:&lt;/p&gt;


&lt;h2&gt;
  
  
  One-Time Commands (Run Only Once)
&lt;/h2&gt;

&lt;p&gt;These are &lt;strong&gt;setup commands&lt;/strong&gt; you only run when creating a &lt;strong&gt;new project&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;create-dagster                        &lt;span class="c"&gt;# Installs the create-dagster tool&lt;/span&gt;
create-dagster project my-first-dagster-project   &lt;span class="c"&gt;# Creates your project folder&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Think of it like &lt;strong&gt;creating a new folder/workspace&lt;/strong&gt; — you only do it once! &lt;a href="https://docs.dagster.io/getting-started/installation" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Every Time You Work on Your Project
&lt;/h2&gt;

&lt;p&gt;These are the commands you run &lt;strong&gt;each time&lt;/strong&gt; you open a new terminal session:&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;# 1. Navigate to your project&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;my-first-dagster-project

&lt;span class="c"&gt;# 2. Activate your virtual environment (REQUIRED every new terminal session)&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate

&lt;span class="c"&gt;# 3. Start the Dagster UI&lt;/span&gt;
dg dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://docs.dagster.io/dagster-basics-tutorial/projects" rel="noopener noreferrer"&gt;2&lt;/a&gt;&lt;/p&gt;




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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;When to Run&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pip install create-dagster&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅ Once ever&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;create-dagster project my-project&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅ Once per new project&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;python -m venv .venv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅ Once per new project&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;source .venv/bin/activate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;🔁 Every new terminal session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dg dev&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;🔁 Every time you want to use the UI&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Pro Tip 💡
&lt;/h2&gt;

&lt;p&gt;Since you're on &lt;strong&gt;WSL (Windows)&lt;/strong&gt;, every time you open a new terminal, just run:&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;cd &lt;/span&gt;my-first-dagster-project
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
dg dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you're good to go! 🚀&lt;/p&gt;

&lt;p&gt;You're now running your first Dagster pipeline! Feel free to ask if you have any questions. 🚀&lt;/p&gt;

</description>
      <category>database</category>
      <category>ai</category>
    </item>
    <item>
      <title>How to Install Dagster: WSL &amp; Native Windows Guide for Beginners (Using pip)</title>
      <dc:creator>Sospeter Mong'are</dc:creator>
      <pubDate>Wed, 09 Sep 2026 14:16:18 +0000</pubDate>
      <link>https://dev.to/msnmongare/how-to-install-dagster-wsl-native-windows-guide-for-beginners-using-pip-5cjc</link>
      <guid>https://dev.to/msnmongare/how-to-install-dagster-wsl-native-windows-guide-for-beginners-using-pip-5cjc</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Dagster is a modern data orchestration platform that helps you build, schedule, and monitor data pipelines. In this guide, we'll walk through two ways to install Dagster on a Windows machine using &lt;strong&gt;pip&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Via WSL (Windows Subsystem for Linux)&lt;/strong&gt; - Recommended&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Directly on Native Windows&lt;/strong&gt; - Without WSL&lt;/li&gt;
&lt;/ol&gt;




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

&lt;p&gt;Before we begin, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python 3.10 or higher&lt;/strong&gt; installed (&lt;strong&gt;Python 3.13 is recommended&lt;/strong&gt;) &lt;a href="https://docs.dagster.io/getting-started/installation" rel="noopener noreferrer"&gt;1&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A stable internet connection&lt;/li&gt;
&lt;li&gt;Basic familiarity with the terminal/command prompt&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Method 1: Installing Dagster via WSL (Recommended) 🐧
&lt;/h1&gt;

&lt;p&gt;WSL (Windows Subsystem for Linux) lets you run a Linux environment directly on Windows. This is the &lt;strong&gt;recommended approach&lt;/strong&gt; because Dagster works best in a Linux-like environment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Install WSL
&lt;/h2&gt;

&lt;p&gt;Open &lt;strong&gt;PowerShell as Administrator&lt;/strong&gt; and run:&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;wsl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--install&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will install WSL with &lt;strong&gt;Ubuntu&lt;/strong&gt; by default. Restart your computer when prompted.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Set Up Ubuntu
&lt;/h2&gt;

&lt;p&gt;After restarting:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;Ubuntu&lt;/strong&gt; from the Start Menu&lt;/li&gt;
&lt;li&gt;Create a username and password when prompted&lt;/li&gt;
&lt;li&gt;Update your packages:
&lt;/li&gt;
&lt;/ol&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;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt upgrade &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 3: Install Python
&lt;/h2&gt;

&lt;p&gt;Check if Python is already installed:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;If not installed or version is below 3.10, run:&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;apt &lt;span class="nb"&gt;install &lt;/span&gt;python3 python3-pip python3-venv &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 4: Create Your Dagster Project Folder
&lt;/h2&gt;

&lt;p&gt;Create and navigate into a project folder:&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;mkdir &lt;/span&gt;my-dagster-project
&lt;span class="nb"&gt;cd &lt;/span&gt;my-dagster-project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 5: Create a Virtual Environment
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 6: Activate the Virtual Environment
&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;source&lt;/span&gt; .venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see &lt;code&gt;(.venv)&lt;/code&gt; appear at the beginning of your terminal line. ✅&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: Install Dagster Using pip
&lt;/h2&gt;

&lt;p&gt;Now install Dagster and its dependencies: &lt;a href="https://docs.dagster.io/getting-started/installation" rel="noopener noreferrer"&gt;2&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;dagster dagster-webserver dagster-dg-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This installs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;dagster&lt;/code&gt;&lt;/strong&gt; - The core Dagster library&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;dagster-webserver&lt;/code&gt;&lt;/strong&gt; - The Dagster UI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;dagster-dg-cli&lt;/code&gt;&lt;/strong&gt; - The &lt;code&gt;dg&lt;/code&gt; command line tool&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 8: Scaffold a Dagster Project
&lt;/h2&gt;

&lt;p&gt;Use the &lt;code&gt;create-dagster&lt;/code&gt; CLI to scaffold a new project: &lt;a href="https://docs.dagster.io/getting-started/installation" rel="noopener noreferrer"&gt;3&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;create-dagster project my-project
&lt;span class="nb"&gt;cd &lt;/span&gt;my-project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 9: Install Project Dependencies
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--editable&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--group&lt;/span&gt; dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The &lt;code&gt;--group&lt;/code&gt; argument requires pip 25.1 or higher. If you have an older version, upgrade pip first: &lt;a href="https://docs.dagster.io/getting-started/installation" rel="noopener noreferrer"&gt;4&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 10: Verify the Installation
&lt;/h2&gt;



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

&lt;/div&gt;



&lt;p&gt;You should see the version number printed in your terminal. &lt;a href="https://docs.dagster.io/getting-started/installation" rel="noopener noreferrer"&gt;5&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 11: Launch Dagster
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dg dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open your browser and go to 👉 &lt;strong&gt;&lt;a href="http://127.0.0.1:3000" rel="noopener noreferrer"&gt;http://127.0.0.1:3000&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Every Time You Return (WSL) 🔁
&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;cd &lt;/span&gt;my-dagster-project/my-project
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
dg dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;









&lt;h1&gt;
  
  
  Method 2: Installing Dagster on Native Windows (Without WSL) 🪟
&lt;/h1&gt;

&lt;p&gt;If you prefer not to use WSL, you can install Dagster directly on Windows using &lt;strong&gt;Command Prompt or PowerShell&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Install Python
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://www.python.org/downloads/" rel="noopener noreferrer"&gt;python.org/downloads&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Download &lt;strong&gt;Python 3.13&lt;/strong&gt; (recommended) &lt;a href="https://docs.dagster.io/getting-started/installation" rel="noopener noreferrer"&gt;6&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Run the installer&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Important:&lt;/strong&gt; Check &lt;strong&gt;"Add Python to PATH"&lt;/strong&gt; during installation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Verify the installation:&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;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--version&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2: Upgrade pip
&lt;/h2&gt;

&lt;p&gt;Make sure you have the latest version of pip: &lt;a href="https://docs.dagster.io/getting-started/installation" rel="noopener noreferrer"&gt;7&lt;/a&gt;&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;pip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--upgrade&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pip&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 3: Create Your Dagster Project Folder
&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;mkdir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;my-dagster-project&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;my-dagster-project&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 4: Create a Virtual Environment
&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;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;venv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;venv&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 5: Activate the Virtual Environment
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;venv&lt;/span&gt;&lt;span class="n"&gt;\Scripts\activate&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see &lt;code&gt;(.venv)&lt;/code&gt; appear at the beginning of your terminal line. ✅&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Install Dagster Using pip
&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;pip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dagster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dagster-webserver&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dagster-dg-cli&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://docs.dagster.io/getting-started/installation" rel="noopener noreferrer"&gt;8&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: Scaffold a Dagster Project
&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;create-dagster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;my-project&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;my-project&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 8: Install Project Dependencies
&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;pip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--editable&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="nt"&gt;--group&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The &lt;code&gt;--group&lt;/code&gt; argument requires &lt;strong&gt;pip 25.1 or higher&lt;/strong&gt;. If you have an older version, omit &lt;code&gt;--group dev&lt;/code&gt; and install the CLI separately: &lt;a href="https://docs.dagster.io/getting-started/installation" rel="noopener noreferrer"&gt;9&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dagster-dg-cli&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 9: Verify the Installation
&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;dg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--version&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the version number of &lt;code&gt;dg&lt;/code&gt; printed. &lt;a href="https://docs.dagster.io/getting-started/installation" rel="noopener noreferrer"&gt;10&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 10: Launch Dagster
&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;dg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open your browser and go to 👉 &lt;strong&gt;&lt;a href="http://127.0.0.1:3000" rel="noopener noreferrer"&gt;http://127.0.0.1:3000&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Every Time You Return (Native Windows) 🔁
&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;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;my-dagster-project\my-project&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;venv&lt;/span&gt;&lt;span class="n"&gt;\Scripts\activate&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;dg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;









&lt;h1&gt;
  
  
  WSL vs Native Windows: Which Should You Choose?
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Factor&lt;/th&gt;
&lt;th&gt;WSL 🐧&lt;/th&gt;
&lt;th&gt;Native Windows 🪟&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Recommended by Dagster&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;⚠️ Works but less common&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ease of Setup&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Performance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Better&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Linux Commands&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ Supported&lt;/td&gt;
&lt;td&gt;❌ Not supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Community Support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ More resources&lt;/td&gt;
&lt;td&gt;⚠️ Limited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best For&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Developers &amp;amp; Data Engineers&lt;/td&gt;
&lt;td&gt;Beginners on Windows&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  Troubleshooting Common Issues 🔧
&lt;/h1&gt;

&lt;h3&gt;
  
  
  ❌ &lt;code&gt;dg&lt;/code&gt; command not found
&lt;/h3&gt;

&lt;p&gt;Make sure your virtual environment is activated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WSL: &lt;code&gt;source .venv/bin/activate&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Windows: &lt;code&gt;.venv\Scripts\activate&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ❌ Python version too old
&lt;/h3&gt;

&lt;p&gt;Make sure you have &lt;strong&gt;Python 3.10+&lt;/strong&gt; installed. &lt;a href="https://docs.dagster.io/getting-started/installation" rel="noopener noreferrer"&gt;11&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  ❌ pip version too old
&lt;/h3&gt;

&lt;p&gt;Upgrade pip with:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  ❌ Port 3000 already in use
&lt;/h3&gt;

&lt;p&gt;Run Dagster on a different port:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dg dev &lt;span class="nt"&gt;-p&lt;/span&gt; 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ❌ Still stuck?
&lt;/h3&gt;

&lt;p&gt;Reach out to the &lt;strong&gt;Dagster community&lt;/strong&gt; for help at &lt;a href="https://dagster.io/community" rel="noopener noreferrer"&gt;dagster.io/community&lt;/a&gt; &lt;a href="https://docs.dagster.io/getting-started/installation" rel="noopener noreferrer"&gt;12&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;WSL 🐧&lt;/th&gt;
&lt;th&gt;Native Windows 🪟&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Install Python&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sudo apt install python3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Download from python.org&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Create venv&lt;/td&gt;
&lt;td&gt;&lt;code&gt;python3 -m venv .venv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;python -m venv .venv&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Activate venv&lt;/td&gt;
&lt;td&gt;&lt;code&gt;source .venv/bin/activate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.venv\Scripts\activate&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Install Dagster&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pip install dagster dagster-webserver dagster-dg-cli&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pip install dagster dagster-webserver dagster-dg-cli&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Launch UI&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dg dev&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dg dev&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;You are now ready to start building amazing data pipelines with Dagster! 🚀&lt;/p&gt;

&lt;p&gt;Happy orchestrating! 🎉&lt;/p&gt;

</description>
      <category>powerplatform</category>
      <category>data</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>Running AI Models Locally on Windows</title>
      <dc:creator>Sospeter Mong'are</dc:creator>
      <pubDate>Sat, 05 Sep 2026 04:39:25 +0000</pubDate>
      <link>https://dev.to/msnmongare/running-ai-models-locally-on-windows-a-beginners-guide-3n3l</link>
      <guid>https://dev.to/msnmongare/running-ai-models-locally-on-windows-a-beginners-guide-3n3l</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;What if you could run your own AI assistant - one that works without the internet, costs nothing per message, never shares your data with anyone, and is available any time you need it? No subscriptions. No monthly limits. No worrying about what happens to your conversations.&lt;/p&gt;

&lt;p&gt;That is exactly what running AI models locally means. Thanks to a free tool called &lt;strong&gt;Ollama&lt;/strong&gt;, you can download and run powerful AI language models directly on your Windows laptop or desktop - the same way you would install any other application.&lt;/p&gt;

&lt;p&gt;This guide will walk you through everything from scratch. No prior experience required. By the end, you will have your own AI assistant running on your machine, accessible both from the terminal and from a full browser-based chat interface that looks and feels just like ChatGPT.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Would You Want to Do This?
&lt;/h2&gt;

&lt;p&gt;If ChatGPT and Claude already exist, why bother running something locally? Here are the real reasons people do it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Complete Privacy&lt;/strong&gt;&lt;br&gt;
When you type into ChatGPT or Claude, your messages are sent to servers owned by OpenAI or Anthropic. Those companies may use your conversations to improve their models, and your data lives on their infrastructure. With a local model, your prompts never leave your computer. Not one word. This matters enormously when dealing with sensitive work documents, client data, medical information, or anything confidential.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Zero Cost&lt;/strong&gt;&lt;br&gt;
ChatGPT Plus costs around $20 per month. Claude Pro is similar. API usage costs money per request. Local models cost absolutely nothing after the initial download. You can run millions of messages and the bill stays at zero.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Works Completely Offline&lt;/strong&gt;&lt;br&gt;
Flying with no Wi-Fi? In a location with poor internet? Your local model does not care. Once downloaded, it runs entirely from your machine. No network required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. No Rate Limits or Message Caps&lt;/strong&gt;&lt;br&gt;
Cloud AI services throttle heavy users. You hit a limit, you wait. Local models have no such restriction. Run them as hard as you want for as long as you want.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Always Available&lt;/strong&gt;&lt;br&gt;
No service outages, no maintenance windows, no "ChatGPT is at capacity right now." Your model is there whenever you open your terminal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Learning and Experimentation&lt;/strong&gt;&lt;br&gt;
Developers, researchers, and curious professionals use local models to experiment with AI, test prompts, build applications, and understand how these systems work - all without racking up API costs.&lt;/p&gt;


&lt;h2&gt;
  
  
  What You Need Before Starting
&lt;/h2&gt;

&lt;p&gt;You do not need a powerful gaming machine. Here is what matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Operating System:&lt;/strong&gt; Windows 10 or Windows 11&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAM:&lt;/strong&gt; 8 GB minimum, 16 GB recommended&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free Disk Space:&lt;/strong&gt; At least 5 GB, ideally 15 GB or more&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPU:&lt;/strong&gt; Not required - models run on your CPU&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internet:&lt;/strong&gt; Only needed for the initial download&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;RAM is the most important factor. The more you have, the better the model you can run:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;8 GB RAM - run 1B to 2B models. Fast but basic.&lt;/li&gt;
&lt;li&gt;16 GB RAM - run 2B to 4B models. Good balance of speed and quality.&lt;/li&gt;
&lt;li&gt;32 GB RAM - run 7B to 13B models. Noticeably more capable.&lt;/li&gt;
&lt;li&gt;48 GB RAM or more - run 70B models. Closest to GPT-4 quality locally.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Understanding Model Sizes
&lt;/h2&gt;

&lt;p&gt;When you see terms like "2B" or "7B", the B stands for &lt;strong&gt;billion parameters&lt;/strong&gt;. Parameters are the internal numbers that make up the model's knowledge and reasoning ability. More parameters generally means more capable responses, larger file size, more RAM required, and slower responses on CPU.&lt;/p&gt;

&lt;p&gt;Think of it like engine size in a car. A 2B model is a small efficient engine - fast and economical. A 70B model is a high-performance engine - far more powerful but needs much more fuel (RAM) and takes longer to get going. For most everyday tasks on a standard laptop, a 3B to 7B model hits the sweet spot perfectly.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Models Available on Ollama and What They Are Best At
&lt;/h2&gt;

&lt;p&gt;Models come in different sizes. The bigger the parameter count, the more capable but the more RAM and time required. Here is a full breakdown from smallest to largest.&lt;/p&gt;


&lt;h3&gt;
  
  
  Small Models - 1B to 4B Parameters
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Best for: laptops with 8–16 GB RAM, everyday tasks, fast responses&lt;/em&gt;&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;gemma2:2b - Google Gemma 2 (2 Billion)&lt;/strong&gt;&lt;br&gt;
Size: ~1.6 GB | RAM needed: 4–6 GB&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull gemma2:2b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Google's lightweight model. The fastest option on CPU. Ideal for quick questions, short summaries, and simple drafts. Not suited for complex reasoning but excellent when speed matters more than depth.&lt;/p&gt;

&lt;p&gt;Best used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quick factual questions&lt;/li&gt;
&lt;li&gt;Summarising short pieces of text&lt;/li&gt;
&lt;li&gt;Simple email drafting&lt;/li&gt;
&lt;li&gt;When you need a fast answer and don't need deep reasoning&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;phi3:mini - Microsoft Phi-3 Mini (3.8 Billion)&lt;/strong&gt;&lt;br&gt;
Size: ~2.2 GB | RAM needed: 6–8 GB&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull phi3:mini
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trained on high-quality textbook-style content. Punches well above its weight on structured tasks. Best small model for code, JSON output, and step-by-step reasoning. If you are a developer this should be your first download.&lt;/p&gt;

&lt;p&gt;Best used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Coding questions and debugging&lt;/li&gt;
&lt;li&gt;Structured outputs like JSON or formatted data&lt;/li&gt;
&lt;li&gt;Step-by-step explanations&lt;/li&gt;
&lt;li&gt;Maths and logic problems&lt;/li&gt;
&lt;li&gt;Following detailed instructions precisely&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;llama3.2:3b - Meta Llama 3.2 (3 Billion)&lt;/strong&gt;&lt;br&gt;
Size: ~2.0 GB | RAM needed: 6–8 GB&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull llama3.2:3b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meta's latest small model and the best all-rounder in the 3B range. Handles a wide variety of tasks reliably. This is the model most people should start with.&lt;/p&gt;

&lt;p&gt;Best used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;General conversation and everyday questions&lt;/li&gt;
&lt;li&gt;Writing assistance - emails, reports, summaries&lt;/li&gt;
&lt;li&gt;Explaining complex topics in plain language&lt;/li&gt;
&lt;li&gt;Brainstorming and idea generation&lt;/li&gt;
&lt;li&gt;Basic coding help&lt;/li&gt;
&lt;li&gt;Translation and language tasks&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;qwen2.5:3b - Alibaba Qwen 2.5 (3 Billion)&lt;/strong&gt;&lt;br&gt;
Size: ~1.9 GB | RAM needed: 6–8 GB&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull qwen2.5:3b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alibaba's small model with exceptional multilingual capability and reliable structured output. If you work in multiple languages or need consistent JSON and formatted data, this is your pick at the small end.&lt;/p&gt;

&lt;p&gt;Best used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Structured data extraction&lt;/li&gt;
&lt;li&gt;JSON and formatted output tasks&lt;/li&gt;
&lt;li&gt;Multilingual tasks - works well in multiple languages including non-English ones&lt;/li&gt;
&lt;li&gt;Data classification and categorisation&lt;/li&gt;
&lt;li&gt;Anything where consistent output format matters&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;mistral:7b - Mistral AI (7 Billion)&lt;/strong&gt;&lt;br&gt;
Size: ~4.1 GB | RAM needed: 8–12 GB&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull mistral:7b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One of the most popular open source models ever released. Mistral 7B outperforms many models twice its size. Excellent all-rounder that handles nuanced writing, reasoning, and coding far better than the 3B models. If your machine has 16 GB RAM, start here instead of the smaller models. The quality jump from 3B to 7B is very noticeable.&lt;/p&gt;

&lt;p&gt;Best used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Everything llama3.2:3b does, but better&lt;/li&gt;
&lt;li&gt;Nuanced writing with more depth and flow&lt;/li&gt;
&lt;li&gt;Coding with fewer errors&lt;/li&gt;
&lt;li&gt;Longer documents where context matters&lt;/li&gt;
&lt;li&gt;When you want noticeably better quality and have the RAM&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Medium Models - 7B to 13B Parameters
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Best for: machines with 16 GB RAM, more complex tasks, better reasoning&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;llama3.1:8b - Meta Llama 3.1 (8 Billion)&lt;/strong&gt;&lt;br&gt;
Size: ~4.7 GB | RAM needed: 10–14 GB&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull llama3.1:8b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One of Meta's strongest open source releases. At 8B parameters it handles complex instructions, long documents, nuanced writing, and multi-step reasoning much better than the 3B version. This is the sweet spot for most people with 16 GB RAM - capable enough for serious work, still manageable on CPU.&lt;/p&gt;

&lt;p&gt;Best used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complex multi-step tasks and analysis&lt;/li&gt;
&lt;li&gt;Long document summarisation&lt;/li&gt;
&lt;li&gt;Detailed writing with nuance&lt;/li&gt;
&lt;li&gt;Research assistance&lt;/li&gt;
&lt;li&gt;Technical explanations&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;gemma2:9b - Google Gemma 2 (9 Billion)&lt;/strong&gt;&lt;br&gt;
Size: ~5.4 GB | RAM needed: 12–16 GB&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull gemma2:9b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bigger sibling of gemma2:2b. Substantially more capable while still being efficient. Strong at instruction following, summarisation of long texts, and factual question answering. Google specifically optimised this size for local deployment.&lt;/p&gt;

&lt;p&gt;Best used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Summarising long reports and documents&lt;/li&gt;
&lt;li&gt;Factual question answering with better accuracy&lt;/li&gt;
&lt;li&gt;Instruction-following tasks requiring precision&lt;/li&gt;
&lt;li&gt;When you want Google-quality output locally&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;deepseek-coder:6.7b - DeepSeek Coder (6.7 Billion)&lt;/strong&gt;&lt;br&gt;
Size: ~3.8 GB | RAM needed: 8–10 GB&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull deepseek-coder:6.7b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A coding-specialised model that performs remarkably well for its size. Strong at Python, JavaScript, and SQL in particular. A practical alternative to CodeLlama if you want solid code assistance without large RAM requirements.&lt;/p&gt;

&lt;p&gt;Best used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python, JavaScript, SQL code generation&lt;/li&gt;
&lt;li&gt;Debugging and error fixing&lt;/li&gt;
&lt;li&gt;Code explanation and documentation&lt;/li&gt;
&lt;li&gt;Writing unit tests&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;codellama:13b - Meta Code Llama (13 Billion)&lt;/strong&gt;&lt;br&gt;
Size: ~7.4 GB | RAM needed: 14–18 GB&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull codellama:13b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Built specifically for code. If you are a developer and code assistance is your primary use case, CodeLlama at 13B is one of the best local options available. Handles Python, JavaScript, SQL, and many other languages. Can explain existing code, generate new code, and debug errors. Not designed for general conversation.&lt;/p&gt;

&lt;p&gt;Best used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Serious code generation across many languages&lt;/li&gt;
&lt;li&gt;Explaining and refactoring existing codebases&lt;/li&gt;
&lt;li&gt;Generating tests and documentation&lt;/li&gt;
&lt;li&gt;Complex debugging&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;phi3:medium - Microsoft Phi-3 Medium (14 Billion)&lt;/strong&gt;&lt;br&gt;
Size: ~7.9 GB | RAM needed: 14–18 GB&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull phi3:medium
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Microsoft's larger Phi-3 model. Takes everything phi3:mini does well - structured reasoning, code, precise instructions - and does it significantly better. Recommended for developers who need reliable code generation or anyone doing complex data tasks.&lt;/p&gt;

&lt;p&gt;Best used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Advanced coding and structured output&lt;/li&gt;
&lt;li&gt;Complex step-by-step reasoning&lt;/li&gt;
&lt;li&gt;Data tasks requiring high precision&lt;/li&gt;
&lt;li&gt;When phi3:mini is good but not quite good enough&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;mistral-nemo:12b - Mistral AI (12 Billion)&lt;/strong&gt;&lt;br&gt;
Size: ~7.1 GB | RAM needed: 14–18 GB&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull mistral-nemo:12b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mistral's newer medium model built in collaboration with NVIDIA. Very strong at instruction following, long context understanding, and technical writing. A step up from mistral:7b in quality while remaining practical on a 16 GB machine.&lt;/p&gt;

&lt;p&gt;Best used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Long context tasks where the model needs to remember a lot&lt;/li&gt;
&lt;li&gt;Technical documentation and writing&lt;/li&gt;
&lt;li&gt;Detailed instruction following&lt;/li&gt;
&lt;li&gt;Professional report drafting&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;llava:13b - LLaVA Multimodal (13 Billion)&lt;/strong&gt;&lt;br&gt;
Size: ~8.0 GB | RAM needed: 14–18 GB&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull llava:13b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One of the very few local models that can look at images. Send it a photo, screenshot, diagram, or chart and ask questions about it. Not as capable as GPT-4 Vision but genuinely useful and completely private. If you need local image understanding, this is your only realistic option at this size.&lt;/p&gt;

&lt;p&gt;Best used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Asking questions about screenshots or photos&lt;/li&gt;
&lt;li&gt;Describing diagrams and charts&lt;/li&gt;
&lt;li&gt;Reading text from images&lt;/li&gt;
&lt;li&gt;Any task where you need to share a visual&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Large Models - 27B to 70B Parameters
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Best for: machines with 32 GB RAM or more, highest quality output, closest to cloud AI&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;gemma2:27b - Google Gemma 2 (27 Billion)&lt;/strong&gt;&lt;br&gt;
Size: ~16 GB | RAM needed: 20–28 GB&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull gemma2:27b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Google's largest Gemma model. Strong across writing, reasoning, and instruction following. Well optimised for local inference compared to other models of similar size. A good first step into large models for anyone with 32 GB RAM.&lt;/p&gt;

&lt;p&gt;Best used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High-quality long-form writing&lt;/li&gt;
&lt;li&gt;Complex analysis and research assistance&lt;/li&gt;
&lt;li&gt;Nuanced reasoning that smaller models struggle with&lt;/li&gt;
&lt;li&gt;Professional-grade document drafting&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;deepseek-r1:32b - DeepSeek (32 Billion)&lt;/strong&gt;&lt;br&gt;
Size: ~19 GB | RAM needed: 24–32 GB&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull deepseek-r1:32b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;DeepSeek's reasoning-focused model that caused a stir when released for matching or beating much larger Western models on reasoning benchmarks. Exceptional at mathematical reasoning, logic problems, and complex analytical thinking.&lt;/p&gt;

&lt;p&gt;Best used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mathematical reasoning and problem solving&lt;/li&gt;
&lt;li&gt;Logic and analytical tasks&lt;/li&gt;
&lt;li&gt;Data interpretation&lt;/li&gt;
&lt;li&gt;Step-by-step complex reasoning chains&lt;/li&gt;
&lt;li&gt;Technical problem solving&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;codellama:34b - Meta Code Llama (34 Billion)&lt;/strong&gt;&lt;br&gt;
Size: ~20 GB | RAM needed: 24–32 GB&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull codellama:34b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The large version of CodeLlama. For serious developers who need the highest quality local code assistance available. Handles complex codebases, generates tests, writes documentation, and debugs across many languages with significantly higher accuracy than the 13B version.&lt;/p&gt;

&lt;p&gt;Best used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enterprise-grade code generation&lt;/li&gt;
&lt;li&gt;Complex multi-file code understanding&lt;/li&gt;
&lt;li&gt;Architecture-level coding decisions&lt;/li&gt;
&lt;li&gt;When codellama:13b is not good enough&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;mixtral:8x7b - Mistral AI Mixtral (47 Billion effective)&lt;/strong&gt;&lt;br&gt;
Size: ~26 GB | RAM needed: 32–48 GB&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull mixtral:8x7b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A clever architecture called Mixture of Experts - it has 47 billion total parameters but only activates around 13 billion at a time, making it faster than a full 47B model. Excellent at complex reasoning, coding, and multilingual tasks. Delivers near GPT-4-level quality for many tasks.&lt;/p&gt;

&lt;p&gt;Best used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complex reasoning and analysis&lt;/li&gt;
&lt;li&gt;High-quality multilingual work&lt;/li&gt;
&lt;li&gt;Advanced coding tasks&lt;/li&gt;
&lt;li&gt;When you want GPT-4-level quality locally and have the RAM&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;llama3.1:70b - Meta Llama 3.1 (70 Billion)&lt;/strong&gt;&lt;br&gt;
Size: ~40 GB | RAM needed: 48 GB or more&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull llama3.1:70b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One of the most capable open source models in existence. Competitive with GPT-4 on many benchmarks. Handles complex legal analysis, long-form writing, advanced reasoning, and nuanced understanding at a level the smaller models simply cannot match. Not realistic on a standard laptop - best run on a high-RAM workstation or a machine with a dedicated GPU.&lt;/p&gt;

&lt;p&gt;Best used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tasks that currently require cloud AI quality&lt;/li&gt;
&lt;li&gt;Complex legal, financial, or medical document analysis&lt;/li&gt;
&lt;li&gt;High-quality long-form content creation&lt;/li&gt;
&lt;li&gt;Advanced multi-step reasoning&lt;/li&gt;
&lt;li&gt;Anything where you need the very best local model available&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Specialised Models
&lt;/h3&gt;




&lt;p&gt;&lt;strong&gt;nomic-embed-text - Nomic (Embedding Model)&lt;/strong&gt;&lt;br&gt;
Size: ~274 MB | RAM needed: Minimal&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull nomic-embed-text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not a chat model - an embedding model. Used by developers to convert text into numerical vectors for semantic search, document similarity, and RAG pipelines. Tiny and fast. Not for conversation but essential for many AI application development workflows.&lt;/p&gt;

&lt;p&gt;Best used for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Building semantic search systems&lt;/li&gt;
&lt;li&gt;Document similarity comparison&lt;/li&gt;
&lt;li&gt;RAG pipeline development&lt;/li&gt;
&lt;li&gt;AI application development&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Full Model Comparison Table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Parameters&lt;/th&gt;
&lt;th&gt;RAM Needed&lt;/th&gt;
&lt;th&gt;Disk Size&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;gemma2:2b&lt;/td&gt;
&lt;td&gt;2B&lt;/td&gt;
&lt;td&gt;4–6 GB&lt;/td&gt;
&lt;td&gt;1.6 GB&lt;/td&gt;
&lt;td&gt;Fast answers, simple tasks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;qwen2.5:3b&lt;/td&gt;
&lt;td&gt;3B&lt;/td&gt;
&lt;td&gt;6–8 GB&lt;/td&gt;
&lt;td&gt;1.9 GB&lt;/td&gt;
&lt;td&gt;Multilingual, structured data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;llama3.2:3b&lt;/td&gt;
&lt;td&gt;3B&lt;/td&gt;
&lt;td&gt;6–8 GB&lt;/td&gt;
&lt;td&gt;2.0 GB&lt;/td&gt;
&lt;td&gt;General all-rounder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;phi3:mini&lt;/td&gt;
&lt;td&gt;3.8B&lt;/td&gt;
&lt;td&gt;6–8 GB&lt;/td&gt;
&lt;td&gt;2.2 GB&lt;/td&gt;
&lt;td&gt;Code, JSON, structured output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mistral:7b&lt;/td&gt;
&lt;td&gt;7B&lt;/td&gt;
&lt;td&gt;8–12 GB&lt;/td&gt;
&lt;td&gt;4.1 GB&lt;/td&gt;
&lt;td&gt;Strong all-rounder, big quality jump&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;deepseek-coder:6.7b&lt;/td&gt;
&lt;td&gt;6.7B&lt;/td&gt;
&lt;td&gt;8–10 GB&lt;/td&gt;
&lt;td&gt;3.8 GB&lt;/td&gt;
&lt;td&gt;Coding, Python, JS, SQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;llama3.1:8b&lt;/td&gt;
&lt;td&gt;8B&lt;/td&gt;
&lt;td&gt;10–14 GB&lt;/td&gt;
&lt;td&gt;4.7 GB&lt;/td&gt;
&lt;td&gt;Complex tasks, long documents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gemma2:9b&lt;/td&gt;
&lt;td&gt;9B&lt;/td&gt;
&lt;td&gt;12–16 GB&lt;/td&gt;
&lt;td&gt;5.4 GB&lt;/td&gt;
&lt;td&gt;Summarisation, factual QA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mistral-nemo:12b&lt;/td&gt;
&lt;td&gt;12B&lt;/td&gt;
&lt;td&gt;14–18 GB&lt;/td&gt;
&lt;td&gt;7.1 GB&lt;/td&gt;
&lt;td&gt;Long context, technical writing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;codellama:13b&lt;/td&gt;
&lt;td&gt;13B&lt;/td&gt;
&lt;td&gt;14–18 GB&lt;/td&gt;
&lt;td&gt;7.4 GB&lt;/td&gt;
&lt;td&gt;Code generation and debugging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;llava:13b&lt;/td&gt;
&lt;td&gt;13B&lt;/td&gt;
&lt;td&gt;14–18 GB&lt;/td&gt;
&lt;td&gt;8.0 GB&lt;/td&gt;
&lt;td&gt;Image understanding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;phi3:medium&lt;/td&gt;
&lt;td&gt;14B&lt;/td&gt;
&lt;td&gt;14–18 GB&lt;/td&gt;
&lt;td&gt;7.9 GB&lt;/td&gt;
&lt;td&gt;Advanced code and reasoning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gemma2:27b&lt;/td&gt;
&lt;td&gt;27B&lt;/td&gt;
&lt;td&gt;20–28 GB&lt;/td&gt;
&lt;td&gt;16 GB&lt;/td&gt;
&lt;td&gt;High quality general use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;deepseek-r1:32b&lt;/td&gt;
&lt;td&gt;32B&lt;/td&gt;
&lt;td&gt;24–32 GB&lt;/td&gt;
&lt;td&gt;19 GB&lt;/td&gt;
&lt;td&gt;Math, logic, analytical reasoning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;codellama:34b&lt;/td&gt;
&lt;td&gt;34B&lt;/td&gt;
&lt;td&gt;24–32 GB&lt;/td&gt;
&lt;td&gt;20 GB&lt;/td&gt;
&lt;td&gt;Enterprise-grade coding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mixtral:8x7b&lt;/td&gt;
&lt;td&gt;47B eff.&lt;/td&gt;
&lt;td&gt;32–48 GB&lt;/td&gt;
&lt;td&gt;26 GB&lt;/td&gt;
&lt;td&gt;Near GPT-4 quality&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;llama3.1:70b&lt;/td&gt;
&lt;td&gt;70B&lt;/td&gt;
&lt;td&gt;48 GB+&lt;/td&gt;
&lt;td&gt;40 GB&lt;/td&gt;
&lt;td&gt;Highest quality, needs workstation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;nomic-embed-text&lt;/td&gt;
&lt;td&gt;Small&lt;/td&gt;
&lt;td&gt;Minimal&lt;/td&gt;
&lt;td&gt;274 MB&lt;/td&gt;
&lt;td&gt;Text embeddings for developers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Quick Reference - Which Model for Which Task
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Best Model&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Quick everyday questions&lt;/td&gt;
&lt;td&gt;gemma2:2b&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Writing emails and reports&lt;/td&gt;
&lt;td&gt;llama3.2:3b or mistral:7b&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coding and debugging&lt;/td&gt;
&lt;td&gt;phi3:mini or deepseek-coder:6.7b&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Serious code generation&lt;/td&gt;
&lt;td&gt;codellama:13b or codellama:34b&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Structured data and JSON&lt;/td&gt;
&lt;td&gt;qwen2.5:3b&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multilingual tasks&lt;/td&gt;
&lt;td&gt;qwen2.5:3b or mistral-nemo:12b&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long document analysis&lt;/td&gt;
&lt;td&gt;llama3.1:8b or mistral-nemo:12b&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mathematical reasoning&lt;/td&gt;
&lt;td&gt;deepseek-r1:32b&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image understanding&lt;/td&gt;
&lt;td&gt;llava:13b&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fastest response&lt;/td&gt;
&lt;td&gt;gemma2:2b&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best overall on 16 GB RAM&lt;/td&gt;
&lt;td&gt;mistral:7b or llama3.1:8b&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best overall on 32 GB RAM&lt;/td&gt;
&lt;td&gt;deepseek-r1:32b or gemma2:27b&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Closest to GPT-4 quality&lt;/td&gt;
&lt;td&gt;llama3.1:70b or mixtral:8x7b&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What Should You Run Given Your Machine?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;8 GB RAM:&lt;/strong&gt;&lt;br&gt;
Stick to &lt;code&gt;gemma2:2b&lt;/code&gt;, &lt;code&gt;llama3.2:3b&lt;/code&gt;, or &lt;code&gt;phi3:mini&lt;/code&gt;. These run comfortably and handle most everyday tasks well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;16 GB RAM (most laptops):&lt;/strong&gt;&lt;br&gt;
Start with &lt;code&gt;llama3.2:3b&lt;/code&gt; or &lt;code&gt;mistral:7b&lt;/code&gt;. Add &lt;code&gt;phi3:mini&lt;/code&gt; for code tasks and &lt;code&gt;deepseek-coder:6.7b&lt;/code&gt; if you write a lot of code. You can also push to &lt;code&gt;llama3.1:8b&lt;/code&gt; if you close other applications while running it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;32 GB RAM:&lt;/strong&gt;&lt;br&gt;
You can run &lt;code&gt;llama3.1:8b&lt;/code&gt;, &lt;code&gt;deepseek-r1:32b&lt;/code&gt;, or &lt;code&gt;gemma2:27b&lt;/code&gt; comfortably. This is where local models start feeling genuinely close to cloud AI quality. The difference from 16 GB models is very noticeable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;32 GB RAM + GPU:&lt;/strong&gt;&lt;br&gt;
Ollama automatically uses your GPU if one is present. Models that took 30 seconds on CPU now respond in 2 to 3 seconds. At this point local AI becomes a serious daily driver that rivals cloud services on speed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;48 GB RAM or more:&lt;/strong&gt;&lt;br&gt;
You can run &lt;code&gt;llama3.1:70b&lt;/code&gt; - one of the best open source models available anywhere. At this level you are genuinely competitive with GPT-4 for many tasks, running completely privately and at zero cost per message.&lt;/p&gt;


&lt;h2&gt;
  
  
  Advantages Over ChatGPT, Claude, and Other Cloud AI
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Local (Ollama)&lt;/th&gt;
&lt;th&gt;ChatGPT Plus&lt;/th&gt;
&lt;th&gt;Claude Pro&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Monthly Cost&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;~$20/month&lt;/td&gt;
&lt;td&gt;~$20/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Privacy&lt;/td&gt;
&lt;td&gt;100% local&lt;/td&gt;
&lt;td&gt;Sent to OpenAI&lt;/td&gt;
&lt;td&gt;Sent to Anthropic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Works Offline&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rate Limits&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Response Speed&lt;/td&gt;
&lt;td&gt;Moderate (CPU)&lt;/td&gt;
&lt;td&gt;Very fast&lt;/td&gt;
&lt;td&gt;Very fast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image Input&lt;/td&gt;
&lt;td&gt;llava only&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Web Search&lt;/td&gt;
&lt;td&gt;No (built-in)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model Choice&lt;/td&gt;
&lt;td&gt;Full control&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost at Scale&lt;/td&gt;
&lt;td&gt;Always free&lt;/td&gt;
&lt;td&gt;Expensive&lt;/td&gt;
&lt;td&gt;Expensive&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  Disadvantages - Being Honest
&lt;/h2&gt;

&lt;p&gt;Local models are impressive but they are not a complete replacement for cloud AI in every situation. Here is what you should know:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They are slower.&lt;/strong&gt; Cloud AI runs on massive GPU clusters. Your CPU takes longer to generate responses. Expect 5 to 30 seconds per paragraph depending on model size. Fine for most tasks, but slower than ChatGPT's near-instant replies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Smaller models make more mistakes.&lt;/strong&gt; The 2B to 4B models sometimes confidently state incorrect information. The larger the model the more reliable it becomes, but always verify important facts before acting on them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No real-time information.&lt;/strong&gt; These models were trained on data up to a certain date and cannot browse the internet. They cannot tell you today's news, current prices, or recent events.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Large models need large hardware.&lt;/strong&gt; The most capable models require 32 GB or 48 GB RAM. These are not realistic on a standard laptop. If you only have 16 GB you are limited to the small and medium range.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First run is slow.&lt;/strong&gt; The first time you run a model after starting Ollama, it loads from disk into RAM. This can take 10 to 30 seconds. After that, responses are faster.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 1 - Install Ollama on Windows
&lt;/h2&gt;

&lt;p&gt;Open your browser and go to &lt;code&gt;https://ollama.com/download&lt;/code&gt; and click &lt;strong&gt;Download for Windows&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Run the installer called &lt;code&gt;OllamaSetup.exe&lt;/code&gt;. It installs quietly and starts a local server on your machine at &lt;code&gt;http://localhost:11434&lt;/code&gt;. You will see the Ollama icon appear in your system tray.&lt;/p&gt;

&lt;p&gt;Verify it installed correctly by opening PowerShell and running:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;You should see a version number printed. If you see an error, close and reopen PowerShell and try again.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2 - Download Your First Model
&lt;/h2&gt;

&lt;p&gt;In PowerShell, run this to download the recommended all-rounder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull llama3.2:3b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then download others as needed. Each is around 2 to 8 GB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull mistral:7b
ollama pull phi3:mini
ollama pull gemma2:2b
ollama pull qwen2.5:3b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See all downloaded models on your machine with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 3 - Chat via the Terminal
&lt;/h2&gt;

&lt;p&gt;Start a conversation directly in PowerShell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama run llama3.2:3b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will see a &lt;code&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt; prompt. Just type your question and press Enter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; What is the difference between RAM and storage?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model responds. Ask follow-up questions just like ChatGPT. To exit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; /bye
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try the other models the same way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama run mistral:7b
ollama run phi3:mini
ollama run gemma2:2b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useful terminal commands:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ollama list&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Show all downloaded models&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ollama run llama3.2:3b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Start chatting with a model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ollama pull mistral:7b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Download a new model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ollama rm gemma2:2b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delete a model to free disk space&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ollama serve&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Manually start the API if it stopped&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Step 4 - Use a Proper Chat Interface in the Browser
&lt;/h2&gt;

&lt;p&gt;The terminal works but a full browser-based interface is much more comfortable for everyday use. &lt;strong&gt;Open WebUI&lt;/strong&gt; gives you a complete ChatGPT-style interface - model selector, conversation history, dark mode, everything - running entirely on your machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install Open WebUI:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;open-webui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Start it:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;open-webui serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Leave this terminal window open. Then open your browser and go to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On your first visit you will be asked to create a local account. Use any username, email, and password you like - this account is stored entirely on your machine, nothing goes anywhere.&lt;/p&gt;

&lt;p&gt;Once logged in you will see a full chat interface. Click the model dropdown at the top and select whichever model you want, then start chatting normally.&lt;/p&gt;

&lt;p&gt;To stop Open WebUI, press &lt;code&gt;Ctrl + C&lt;/code&gt; in the terminal window running it. To start it again next time, run &lt;code&gt;open-webui serve&lt;/code&gt; and visit &lt;code&gt;http://localhost:8080&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tips for Getting Better Responses
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Be specific about what you want.&lt;/strong&gt;&lt;br&gt;
Instead of "help me with this email" try "rewrite this email in a professional tone, keep it under 100 words, and make the call to action clear."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tell it the format you want.&lt;/strong&gt;&lt;br&gt;
"Explain this in bullet points." "Give me a table comparing these options." "Respond in plain language for a non-technical audience."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Give it context.&lt;/strong&gt;&lt;br&gt;
"I am a software engineer working on a Python API. Explain this error message and suggest a fix."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ask it to think step by step.&lt;/strong&gt;&lt;br&gt;
Simply adding "walk me through this step by step" significantly improves reasoning quality on smaller models.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Switch models for different tasks.&lt;/strong&gt;&lt;br&gt;
Use gemma2:2b for quick questions, phi3:mini or deepseek-coder for code, llama3.2:3b or mistral:7b for general writing, qwen2.5:3b for structured output, deepseek-r1:32b for analytical reasoning if your machine can handle it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Upgrade your model when results disappoint.&lt;/strong&gt;&lt;br&gt;
If a response is not good enough, try the next size up. The quality difference between a 3B and 8B model on the same task is often significant.&lt;/p&gt;




&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does it use the internet after the models are downloaded?&lt;/strong&gt;&lt;br&gt;
No. Everything runs completely offline. Nothing you type leaves your machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will it slow down my computer?&lt;/strong&gt;&lt;br&gt;
During inference your CPU usage will spike for a few seconds while generating a response. Other tasks may feel slightly sluggish during that time. Afterwards it returns to normal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use it for sensitive work documents?&lt;/strong&gt;&lt;br&gt;
Yes. Nothing leaves your machine, making it suitable for confidential documents, client data, and internal company information.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if I want a more capable model than the ones listed?&lt;/strong&gt;&lt;br&gt;
Browse the full library at &lt;code&gt;https://ollama.com/library&lt;/code&gt;. New models are added regularly as the open source AI community releases them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I update a model when a newer version comes out?&lt;/strong&gt;&lt;br&gt;
Just pull it again: &lt;code&gt;ollama pull llama3.2:3b&lt;/code&gt;. Ollama downloads the updated version automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I run multiple models at the same time?&lt;/strong&gt;&lt;br&gt;
Yes, but each model loaded into RAM takes up its full allocation. On 16 GB RAM, running two 3B models simultaneously would use most of your available memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if I have a GPU?&lt;/strong&gt;&lt;br&gt;
Ollama detects your GPU automatically and uses it. You do not need to configure anything. Response times drop dramatically - from 15 to 30 seconds down to 1 to 3 seconds on a mid-range GPU.&lt;/p&gt;




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

&lt;p&gt;Running AI models locally on Windows is simpler than most people expect. Install one tool, download a model, and you are ready to go. The whole setup takes under 30 minutes.&lt;/p&gt;

&lt;p&gt;You now have a private, free, offline AI assistant that runs on hardware you already own. It will not replace cloud AI for every task - complex reasoning and real-time information are still better handled by GPT-4 or Claude - but for the majority of everyday questions, writing assistance, coding help, and data tasks, your local model will serve you very well.&lt;/p&gt;

&lt;p&gt;Start with &lt;code&gt;llama3.2:3b&lt;/code&gt; for general use. Move to &lt;code&gt;mistral:7b&lt;/code&gt; when you want better quality. Add &lt;code&gt;phi3:mini&lt;/code&gt; for code. Explore the larger models as your needs grow. And enjoy having your own AI that answers to nobody but you.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Browse the full model library at &lt;code&gt;https://ollama.com/library&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>openai</category>
      <category>claude</category>
      <category>pgaichallenge</category>
    </item>
    <item>
      <title>KCB Funds Transfer API</title>
      <dc:creator>Sospeter Mong'are</dc:creator>
      <pubDate>Mon, 31 Aug 2026 15:00:28 +0000</pubDate>
      <link>https://dev.to/msnmongare/kcb-funds-transfer-api-4ja8</link>
      <guid>https://dev.to/msnmongare/kcb-funds-transfer-api-4ja8</guid>
      <description>&lt;p&gt;The KCB Funds Transfer API enables businesses and applications to securely initiate funds transfers programmatically without requiring manual intervention through the KCB banking platform (Mobile app and Web). This guide walks you through the API, request parameters, authentication, transaction flow, and how to successfully integrate and test a funds transfer.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is the KCB Funds Transfer API?
&lt;/h2&gt;

&lt;p&gt;Banks have always let you move money through their apps or teller counters. The &lt;a href="https://sandbox.buni.kcbgroup.com/devportal/apis/372552ef-5ebd-4921-9a0d-2f3b1da8cb86/overview" rel="noopener noreferrer"&gt;KCB Funds Transfer (FT) API&lt;/a&gt; lets you do that same thing &lt;strong&gt;from your own application&lt;/strong&gt; - by writing code.&lt;/p&gt;

&lt;p&gt;Instead of a human clicking "Send Money" in the KCB app, your system sends an HTTP request to KCB's servers, and the money moves. That's it at the core.&lt;/p&gt;

&lt;p&gt;This is useful when you're building:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A business payment system (e.g. auto-paying suppliers)&lt;/li&gt;
&lt;li&gt;A SaaS platform that needs to disburse funds to users&lt;/li&gt;
&lt;li&gt;A financial dashboard that triggers bank transfers&lt;/li&gt;
&lt;li&gt;Any system where money movement needs to be automated&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What Can It Actually Do?
&lt;/h2&gt;

&lt;p&gt;The KCB FT API supports four types of money movement:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Transfer Type&lt;/th&gt;
&lt;th&gt;What It Means&lt;/th&gt;
&lt;th&gt;Code&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Internal Transfer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Between two KCB accounts&lt;/td&gt;
&lt;td&gt;&lt;code&gt;IF&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RTGS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Large-value transfers to other banks&lt;/td&gt;
&lt;td&gt;&lt;code&gt;RT&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;EFT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Standard transfers to other banks&lt;/td&gt;
&lt;td&gt;&lt;code&gt;EF&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pesalink&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bank-to-bank transfers within Kenya's IPS&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PL&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mobile Money&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;From KCB account to M-PESA wallet&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MO&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You pick the type that fits your use case and pass the right code in your request.&lt;/p&gt;




&lt;h2&gt;
  
  
  Before You Write Any Code
&lt;/h2&gt;

&lt;p&gt;There are two environments: &lt;strong&gt;Sandbox&lt;/strong&gt; (for testing) and &lt;strong&gt;Production&lt;/strong&gt; (real money). Always start with Sandbox.&lt;/p&gt;

&lt;h3&gt;
  
  
  To Get Started on Sandbox
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Register on the KCB Buni developer portal: &lt;a href="https://sandbox.buni.kcbgroup.com/devportal/apis" rel="noopener noreferrer"&gt;sandbox.buni.kcbgroup.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Create an application on the portal&lt;/li&gt;
&lt;li&gt;Subscribe to the FT API - you'll get a &lt;strong&gt;Consumer Key&lt;/strong&gt; and a &lt;strong&gt;Consumer Secret&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Have a &lt;strong&gt;Callback URL&lt;/strong&gt; ready (more on this below)&lt;/li&gt;
&lt;li&gt;KCB will whitelist your Buni username and provide test account numbers&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  GOING LIVE (For Production (Real Money))
&lt;/h3&gt;

&lt;p&gt;You'll additionally need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A real KCB Bank account number&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://sandbox.buni.kcbgroup.com/devportal/apis/372552ef-5ebd-4921-9a0d-2f3b1da8cb86/documents/7fa2ec48-09f8-425e-974c-d3a29b3c4c5f" rel="noopener noreferrer"&gt;signed Indemnity Form&lt;/a&gt; (KCB provides the template)&lt;/li&gt;
&lt;li&gt;An &lt;a href="https://sandbox.buni.kcbgroup.com/devportal/apis/372552ef-5ebd-4921-9a0d-2f3b1da8cb86/documents/30794f68-11c3-4881-ae72-9ba541b2ab21" rel="noopener noreferrer"&gt;FT Request Letter&lt;/a&gt; for integration approval&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How the API Works - The Big Picture
&lt;/h2&gt;

&lt;p&gt;The KCB FT API is &lt;strong&gt;asynchronous&lt;/strong&gt;. This is the most important thing to understand as a beginner.&lt;/p&gt;

&lt;p&gt;When you send a transfer request, you don't get back "success" or "failed" immediately. Instead:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You send the transfer request → KCB immediately replies: &lt;em&gt;"Got it, we're processing"&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;KCB processes the transfer in their core banking system&lt;/li&gt;
&lt;li&gt;KCB sends the &lt;strong&gt;result&lt;/strong&gt; to your Callback URL via a POST request&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This means your system needs to be able to &lt;strong&gt;receive incoming requests&lt;/strong&gt; (a webhook endpoint), not just make outgoing ones. If you don't have a callback URL, you won't know whether transfers succeeded or failed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your App  ──POST──▶  KCB FT API  ──▶  Core Banking
                         │
                         └──POST──▶  Your Callback URL
                                      (result arrives here)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 1: Get an Auth Token
&lt;/h2&gt;

&lt;p&gt;Every API call to KCB requires a &lt;strong&gt;Bearer Token&lt;/strong&gt;. You get this by calling the token endpoint using your Consumer Key and Consumer Secret from the Buni portal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST https://uat.buni.kcbgroup.com/token?grant_type=client_credentials
Authorization: Basic &amp;lt;base64(consumerKey:consumerSecret)&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response gives you an access token. Include it as a header in all subsequent requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Authorization: Bearer &amp;lt;your_token_here&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tokens expire, so your application should handle token refresh - request a new one when the old one stops working.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Send a Transfer Request
&lt;/h2&gt;

&lt;p&gt;Once you have a token, you can initiate a funds transfer. Here's what the request body looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"beneficiaryDetails"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"companyCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"KE0010001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"creditAccountNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1234567890"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"KES"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"debitAccountNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"9876543210"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"debitAmount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"paymentDetails"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Invoice #1042 settlement"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transactionReference"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MYAPP20240831001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transactionType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"IF"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"beneficiaryBankCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"01"&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;Let's break down each field:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;What It Is&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;beneficiaryDetails&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full name of who receives the money&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"John Doe"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;companyCode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;KCB's internal code for your bank branch&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"KE0010001"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;creditAccountNumber&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Account that receives the money&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"1234567890"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;currency&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Always &lt;code&gt;KES&lt;/code&gt; for Kenyan Shillings&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"KES"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;debitAccountNumber&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Your KCB account (the one being charged)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"9876543210"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;debitAmount&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Amount to transfer (in KES)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;5000&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;paymentDetails&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Short note about the transfer reason&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"Invoice settlement"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transactionReference&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Your unique ID&lt;/strong&gt; for this transaction&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"MYAPP20240831001"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transactionType&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Type of transfer (see table above)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"IF"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;beneficiaryBankCode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bank code of the recipient's bank&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;"01"&lt;/code&gt; for KCB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The &lt;code&gt;transactionReference&lt;/code&gt; field is critical.&lt;/strong&gt; It must be unique per transaction - never reuse it. This is how you and KCB track the same transaction. A good pattern: prefix + date + sequential number, e.g. &lt;code&gt;PAY20240831001&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 3: Handle the Response
&lt;/h2&gt;

&lt;p&gt;When your request reaches KCB, they send back an immediate acknowledgement. Here's what success looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"statusCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"statusMessage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"statusDescription"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Request received for processing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"merchantID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"263eb626-3fe7-4662-813e-f6f2962219e1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"retrievalRefNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PCI663RSS"&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;&lt;code&gt;statusCode: "0"&lt;/code&gt; means KCB accepted your request. Save the &lt;code&gt;merchantID&lt;/code&gt; - you can use it to reference this transaction later.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;this is not the final result&lt;/strong&gt;. The transfer is still being processed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common Error Responses
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Error&lt;/th&gt;
&lt;th&gt;What It Means&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Missing Credentials&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Token is missing or wrong&lt;/td&gt;
&lt;td&gt;Re-check your Bearer token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Validation failed: Invalid crucial param&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A field is missing or wrong value&lt;/td&gt;
&lt;td&gt;Review your request body&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Retrieval RefNumber already exist&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;You reused a &lt;code&gt;transactionReference&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Generate a new unique reference&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Transaction failed due to limit rule&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Amount exceeds your daily/transaction limit&lt;/td&gt;
&lt;td&gt;Adjust amount or check limits on FT portal&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Step 4: Handle the Callback (The Real Result)
&lt;/h2&gt;

&lt;p&gt;When KCB finishes processing, they POST to your Callback URL. This is what a successful notification looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ftReference"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FT22060GXZGY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transactionDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2022-07-06T11:08:40.019Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transactionStatus"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SUCCESS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transactionMessage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Processed Successfully"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"beneficiaryAccountNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1234567890"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"beneficiaryName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JOHN DOE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transactionReference"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MYAPP20240831001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"merchantId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1d2b13f8-ea62-465c-9fda-e18353579880"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"debitAccountNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"9876543210"&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;Your callback endpoint should:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check &lt;code&gt;transactionStatus&lt;/code&gt; - it'll be &lt;code&gt;SUCCESS&lt;/code&gt; or &lt;code&gt;FAILED&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Match &lt;code&gt;transactionReference&lt;/code&gt; to the transfer you initiated&lt;/li&gt;
&lt;li&gt;Update your database accordingly&lt;/li&gt;
&lt;li&gt;Return an HTTP &lt;code&gt;200 OK&lt;/code&gt; so KCB knows you received the notification&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Always match incoming callbacks to your own records using &lt;code&gt;transactionReference&lt;/code&gt;. Never trust a callback blindly - verify the amount and accounts match what you sent.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Sending Money to M-PESA
&lt;/h2&gt;

&lt;p&gt;To transfer from a KCB account to an M-PESA wallet, use transaction type &lt;code&gt;MO&lt;/code&gt; and set &lt;code&gt;beneficiaryBankCode&lt;/code&gt; to &lt;code&gt;MPESA&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transactionType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MO"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"beneficiaryBankCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MPESA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"creditAccountNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2547XXXXXXXX"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"beneficiaryDetails"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Jane Wanjiku"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&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;The &lt;code&gt;creditAccountNumber&lt;/code&gt; in this case should be the recipient's phone number in the format &lt;code&gt;2547XXXXXXXX&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sending to Other Banks
&lt;/h2&gt;

&lt;p&gt;For inter-bank transfers (EFT, RTGS, Pesalink), you need the correct &lt;strong&gt;bank code&lt;/strong&gt; for the recipient's bank. Here are the most common ones:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bank&lt;/th&gt;
&lt;th&gt;Code&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;KCB&lt;/td&gt;
&lt;td&gt;01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Equity Bank&lt;/td&gt;
&lt;td&gt;68&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Co-op Bank&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NCBA&lt;/td&gt;
&lt;td&gt;07&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stanbic Bank&lt;/td&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I&amp;amp;M Bank&lt;/td&gt;
&lt;td&gt;57&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DTB&lt;/td&gt;
&lt;td&gt;63&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Family Bank&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Absa&lt;/td&gt;
&lt;td&gt;03&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M-PESA&lt;/td&gt;
&lt;td&gt;MPESA&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The full list is available in the API documentation appendix.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Minimal Node.js Example
&lt;/h2&gt;

&lt;p&gt;Here's a simple end-to-end example to get you oriented:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;axios&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="s1"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Step 1: Get a token&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;consumerKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;consumerSecret&lt;/span&gt;&lt;span class="p"&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;credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;consumerKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;consumerSecret&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://uat.buni.kcbgroup.com/token?grant_type=client_credentials&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="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Basic &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Step 2: Initiate a transfer&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendTransfer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&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;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;beneficiaryDetails&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John Doe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;companyCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;KE0010001&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;creditAccountNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1234567890&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;KES&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;debitAccountNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;9876543210&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;debitAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;paymentDetails&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Test payment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;transactionReference&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`PAY&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// unique reference&lt;/span&gt;
    &lt;span class="na"&gt;transactionType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;IF&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;beneficiaryBankCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;01&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://uat.buni.kcbgroup.com/ft/transfer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// confirm exact endpoint from KCB&lt;/span&gt;
    &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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="s1"&gt;application/json&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Step 3: Callback handler (Express example)&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;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/kcb/callback&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="p"&gt;{&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;transactionReference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;transactionStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transactionStatus&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SUCCESS&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;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="s2"&gt;`Transfer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;transactionReference&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; succeeded - KES &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// update your DB here&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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="s2"&gt;`Transfer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;transactionReference&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; FAILED`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// handle failure - notify user, reverse any internal state&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;status&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="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="s1"&gt;OK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// always acknowledge&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Common Beginner Mistakes to Avoid
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Not saving the &lt;code&gt;transactionReference&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
If you don't store it, you can't match the callback to your original request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Confusing "Success" response with a completed transfer&lt;/strong&gt;&lt;br&gt;
A &lt;code&gt;statusCode: "0"&lt;/code&gt; means &lt;em&gt;accepted&lt;/em&gt;, not &lt;em&gt;completed&lt;/em&gt;. Wait for the callback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Not handling duplicate callbacks&lt;/strong&gt;&lt;br&gt;
KCB may retry sending the callback if your server returns a non-200 response. Make your callback handler &lt;strong&gt;idempotent&lt;/strong&gt; - processing the same &lt;code&gt;transactionReference&lt;/code&gt; twice should not double-credit or double-update.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Using production credentials in development&lt;/strong&gt;&lt;br&gt;
Always use Sandbox for testing. Real money moves in Production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Hardcoding the &lt;code&gt;transactionReference&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
It must be unique per request. Generate it dynamically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Detail&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sandbox Portal&lt;/td&gt;
&lt;td&gt;sandbox.buni.kcbgroup.com&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token Endpoint&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POST /token?grant_type=client_credentials&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth Method&lt;/td&gt;
&lt;td&gt;OAuth 2.0 (Basic Auth with Consumer Key + Secret)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request Type&lt;/td&gt;
&lt;td&gt;Async (result comes via callback)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Result Delivery&lt;/td&gt;
&lt;td&gt;POST to your Callback URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Support Email&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:buni@kcbgroup.com"&gt;buni@kcbgroup.com&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Register on the &lt;a href="https://sandbox.buni.kcbgroup.com/devportal/apis" rel="noopener noreferrer"&gt;KCB Buni portal&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Create an application and subscribe to the FT API&lt;/li&gt;
&lt;li&gt;Set up a test callback URL (tools like &lt;a href="https://webhook.site" rel="noopener noreferrer"&gt;Webhook.site&lt;/a&gt; or &lt;a href="https://ngrok.com" rel="noopener noreferrer"&gt;ngrok&lt;/a&gt; are useful here)&lt;/li&gt;
&lt;li&gt;Run a test internal transfer between the sandbox accounts KCB provides&lt;/li&gt;
&lt;li&gt;Inspect the callback payload your server receives&lt;/li&gt;
&lt;li&gt;Once confident, apply for Production access&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;For questions or integration support, reach out to the KCB Buni team at &lt;strong&gt;&lt;a href="mailto:buni@kcbgroup.com"&gt;buni@kcbgroup.com&lt;/a&gt;&lt;/strong&gt; or &lt;a href="https://wa.me/254708920430?text=Hi+Sos%2C+I+need+help+with+KCB+integrations" rel="noopener noreferrer"&gt;Whatsapp Me here&lt;/a&gt;&lt;/p&gt;

</description>
      <category>kcb</category>
      <category>buni</category>
      <category>stripe</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Cron Expressions in Airflow: Understanding `00 3 * * 1-7`</title>
      <dc:creator>Sospeter Mong'are</dc:creator>
      <pubDate>Wed, 26 Aug 2026 07:49:29 +0000</pubDate>
      <link>https://dev.to/msnmongare/cron-expressions-in-airflow-understanding-00-3-1-7-3cff</link>
      <guid>https://dev.to/msnmongare/cron-expressions-in-airflow-understanding-00-3-1-7-3cff</guid>
      <description>&lt;p&gt;If you are working with &lt;strong&gt;Apache Airflow&lt;/strong&gt;, Linux, automation, or data pipelines, you will eventually encounter &lt;strong&gt;cron expressions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At first, something like this can look confusing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00 3 * * 1-7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But once you understand what each part represents, cron schedules become very easy to read and create.&lt;/p&gt;

&lt;p&gt;In this article, we will break down the cron expression &lt;code&gt;00 3 * * 1-7&lt;/code&gt;, explain each component, and show how it is used in Airflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is a Cron Expression?
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;cron expression&lt;/strong&gt; is a format used to define when a scheduled task should run.&lt;/p&gt;

&lt;p&gt;Cron is commonly used in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Linux and Unix systems&lt;/li&gt;
&lt;li&gt;Apache Airflow&lt;/li&gt;
&lt;li&gt;Data engineering pipelines&lt;/li&gt;
&lt;li&gt;Database jobs&lt;/li&gt;
&lt;li&gt;ETL and ELT processes&lt;/li&gt;
&lt;li&gt;Backup automation&lt;/li&gt;
&lt;li&gt;Application maintenance&lt;/li&gt;
&lt;li&gt;Server monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A traditional cron expression contains &lt;strong&gt;five fields&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌───── Minute
│ ┌─── Hour
│ │ ┌─ Day of month
│ │ │ ┌─ Month
│ │ │ │ ┌─ Day of week
│ │ │ │ │
00  3  *  *  1-7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each field tells the scheduler a different part of the schedule.&lt;/p&gt;




&lt;h1&gt;
  
  
  Understanding &lt;code&gt;00 3 * * 1-7&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;Let's break it down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00 3 * * 1-7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Minute&lt;/td&gt;
&lt;td&gt;&lt;code&gt;00&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;At minute 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hour&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;At 3 AM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day of month&lt;/td&gt;
&lt;td&gt;&lt;code&gt;*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Month&lt;/td&gt;
&lt;td&gt;&lt;code&gt;*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day of week&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1-7&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Monday through Sunday&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Therefore:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;00 3 * * 1-7&lt;/code&gt; means run the task every day at 3:00 AM.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. The Minute Field - &lt;code&gt;00&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The first field represents the &lt;strong&gt;minute&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means the task runs at minute zero.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3:00 AM
4:00 AM
5:00 AM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead, the task would run at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3:30 AM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;At exactly 3:00.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  2. The Hour Field - &lt;code&gt;3&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The second field represents the &lt;strong&gt;hour&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means 3 AM.&lt;/p&gt;

&lt;p&gt;Combined with the first field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we get:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;3:00 AM.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;means 1:00 AM.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00 6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;means 6:00 AM.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;30 18
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;means 6:30 PM.&lt;/p&gt;

&lt;p&gt;Cron generally uses the &lt;strong&gt;24-hour clock&lt;/strong&gt;, so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3  = 3 AM
12 = 12 PM
15 = 3 PM
18 = 6 PM
23 = 11 PM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  3. Day of Month - &lt;code&gt;*&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;The third field represents the &lt;strong&gt;day of the month&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The asterisk means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every possible value.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;in this position means every day of the month:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1
2
3
4
...
31
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means there is no restriction based on the date.&lt;/p&gt;




&lt;h1&gt;
  
  
  4. Month - &lt;code&gt;*&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;The fourth field represents the &lt;strong&gt;month&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Again, we have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every month.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Therefore, the schedule applies to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;January
February
March
April
...
December
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no restriction on the month.&lt;/p&gt;




&lt;h1&gt;
  
  
  5. Day of Week - &lt;code&gt;1-7&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;The final field represents the &lt;strong&gt;day of the week&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1-7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The range means Monday through Sunday.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 = Monday
2 = Tuesday
3 = Wednesday
4 = Thursday
5 = Friday
6 = Saturday
7 = Sunday
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1-7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every day of the week.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So the entire expression:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00 3 * * 1-7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Run every day at 3:00 AM.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Cron Schedule Examples
&lt;/h1&gt;

&lt;p&gt;Understanding a few examples makes cron much easier.&lt;/p&gt;

&lt;h3&gt;
  
  
  Every day at 3:00 AM
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00 3 * * *
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meaning:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Run every day at 3:00 AM.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You could also write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00 3 * * 1-7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both express a daily schedule.&lt;/p&gt;

&lt;h3&gt;
  
  
  Every day at 6:30 AM
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;30 6 * * *
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meaning:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Run every day at 6:30 AM.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Every Monday at 3:00 AM
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00 3 * * 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meaning:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Run every Monday at 3:00 AM.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Every Friday at 5:00 PM
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00 17 * * 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meaning:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Run every Friday at 5:00 PM.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Every hour
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00 * * * *
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meaning:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Run at minute 0 of every hour.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1:00
2:00
3:00
4:00
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Every 15 minutes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*/15 * * * *
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meaning:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Run every 15 minutes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;12:00
12:15
12:30
12:45
1:00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Using Cron Expressions in Apache Airflow
&lt;/h1&gt;

&lt;p&gt;Cron expressions are particularly useful in &lt;strong&gt;Apache Airflow&lt;/strong&gt; because they allow you to control when DAGs should run.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&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;airflow&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DAG&lt;/span&gt;

&lt;span class="n"&gt;dag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DAG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;dag_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;daily_pipeline&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;00 3 * * *&lt;/span&gt;&lt;span class="sh"&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;p&gt;This tells Airflow to schedule the DAG:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every day at 3:00 AM.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You may also encounter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;00 3 * * 1-7&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This explicitly specifies Monday through Sunday.&lt;/p&gt;

&lt;p&gt;In this particular case, however, the simpler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;schedule&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;00 3 * * *&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is easier to read because the &lt;code&gt;*&lt;/code&gt; already means every day of the week.&lt;/p&gt;




&lt;h1&gt;
  
  
  Be Careful With Timezones in Airflow
&lt;/h1&gt;

&lt;p&gt;One of the most important things to understand when working with &lt;strong&gt;Airflow schedules&lt;/strong&gt; is the timezone.&lt;/p&gt;

&lt;p&gt;Suppose your Airflow environment uses &lt;strong&gt;UTC&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If your DAG is scheduled for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;03:00 UTC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and you are working in Kenya, which uses &lt;strong&gt;East Africa Time (EAT)&lt;/strong&gt;, that corresponds to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;06:00 AM EAT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So if your intention is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Run the pipeline at 3:00 AM Kenya time&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;you need to make sure your Airflow DAG and environment are configured with the appropriate timezone.&lt;/p&gt;

&lt;p&gt;This is especially important for data engineering teams working across countries and regions.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Cron Expressions Matter in Data Engineering
&lt;/h1&gt;

&lt;p&gt;Cron schedules are commonly used to control the execution of data pipelines.&lt;/p&gt;

&lt;p&gt;For example, imagine you have a pipeline that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Extracts data from a source database&lt;/li&gt;
&lt;li&gt;Loads the data into a data lake&lt;/li&gt;
&lt;li&gt;Transforms the data using dbt&lt;/li&gt;
&lt;li&gt;Updates a data warehouse&lt;/li&gt;
&lt;li&gt;Makes the data available for reporting&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You might schedule the pipeline to run every morning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00 3 * * *
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pipeline would then be triggered every day at 3:00 AM.&lt;/p&gt;

&lt;p&gt;This is particularly useful when data needs to be processed before business users start working in the morning.&lt;/p&gt;




&lt;h1&gt;
  
  
  A Simple Way to Read Cron
&lt;/h1&gt;

&lt;p&gt;Whenever you see a cron expression, read it from left to right:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MINUTE
HOUR
DAY OF MONTH
MONTH
DAY OF WEEK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00 3 * * 1-7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you can translate it mentally as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00    -&amp;gt; minute 0
3     -&amp;gt; hour 3
*     -&amp;gt; every day
*     -&amp;gt; every month
1-7   -&amp;gt; Monday to Sunday
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then combine everything:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Every day at 3:00 AM.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Quick Cron Cheat Sheet
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cron&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;00 3 * * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every day at 3:00 AM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;00 3 * * 1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every Monday at 3:00 AM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;00 3 * * 1-5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Monday to Friday at 3:00 AM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;00 3 * * 1-7&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every day at 3:00 AM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;30 6 * * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every day at 6:30 AM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;00 12 * * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every day at noon&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;00 18 * * 5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every Friday at 6:00 PM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;*/15 * * * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every 15 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;00 * * * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every hour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;00 0 1 * *&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;First day of every month at midnight&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;The cron expression:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00 3 * * 1-7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Run every day of the week at exactly 3:00 AM.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The five cron fields are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00    3    *    *    1-7
│     │    │    │      │
│     │    │    │      └── Day of week
│     │    │    └───────── Month
│     │    └────────────── Day of month
│     └─────────────────── Hour
└───────────────────────── Minute
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you understand these five fields, you can read most basic &lt;strong&gt;Linux cron and Apache Airflow schedules&lt;/strong&gt; without having to memorize them.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>airflow</category>
      <category>openai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Understanding AI Model Pricing</title>
      <dc:creator>Sospeter Mong'are</dc:creator>
      <pubDate>Mon, 24 Aug 2026 12:39:42 +0000</pubDate>
      <link>https://dev.to/msnmongare/understanding-ai-model-pricing-2d4f</link>
      <guid>https://dev.to/msnmongare/understanding-ai-model-pricing-2d4f</guid>
      <description>&lt;p&gt;When you look at AI pricing for example &lt;a href="https://cursor.com/docs/models-and-pricing" rel="noopener noreferrer"&gt;cursor&lt;/a&gt;, you will often see four numbers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pricing component&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;What you send to the AI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cache Write&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Information saved temporarily so it can be reused&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cache Read&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Previously cached information that the AI reads again&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;What the AI generates for you&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are usually priced &lt;strong&gt;per million tokens&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let's break them down.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Input
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Input is everything you give the AI.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This can include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your question&lt;/li&gt;
&lt;li&gt;Your instructions&lt;/li&gt;
&lt;li&gt;Code you provide&lt;/li&gt;
&lt;li&gt;Files you ask it to analyze&lt;/li&gt;
&lt;li&gt;Previous conversation/context&lt;/li&gt;
&lt;li&gt;Relevant parts of your project&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, you tell Cursor:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Fix this Laravel authentication bug."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And Cursor sends your instructions plus relevant code to the AI.&lt;/p&gt;

&lt;p&gt;That information is &lt;strong&gt;input&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;Suppose the model's input price is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$2 per million tokens&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your request uses 10,000 input tokens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10,000 / 1,000,000 × $2
= $0.02
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the input cost would be approximately &lt;strong&gt;$0.02&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  2. Output
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Output is what the AI generates in response.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For a coding assistant, output could include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code&lt;/li&gt;
&lt;li&gt;Explanations&lt;/li&gt;
&lt;li&gt;SQL queries&lt;/li&gt;
&lt;li&gt;Suggestions&lt;/li&gt;
&lt;li&gt;Refactored code&lt;/li&gt;
&lt;li&gt;JSON&lt;/li&gt;
&lt;li&gt;Documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Suppose a model costs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$6 per million output tokens&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If it generates 10,000 tokens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10,000 / 1,000,000 × $6
= $0.06
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the output cost would be approximately &lt;strong&gt;$0.06&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is Output often more expensive?
&lt;/h3&gt;

&lt;p&gt;Generating information is computationally expensive.&lt;/p&gt;

&lt;p&gt;That's why you will often see something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Input: $2/M&lt;br&gt;
Output: $6/M&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI charges differently for what it &lt;strong&gt;reads&lt;/strong&gt; and what it &lt;strong&gt;generates&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  3. Cache Write
&lt;/h1&gt;

&lt;p&gt;This one is slightly more complicated.&lt;/p&gt;

&lt;p&gt;Imagine you are working on a large Laravel project.&lt;/p&gt;

&lt;p&gt;Every time you ask Cursor something, Cursor may need to provide the AI with a lot of context about your project.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Laravel project
+ routes
+ controllers
+ models
+ migrations
+ services
+ configuration
+ existing code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sending the same information repeatedly can be inefficient.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caching allows some of that information to be stored temporarily so it can be reused.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When information is stored in the cache, that is called a:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache Write&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In simple terms:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Cache Write = putting information into the AI's reusable memory/cache.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  4. Cache Read
&lt;/h1&gt;

&lt;p&gt;Once information has been cached, the AI can reuse it.&lt;/p&gt;

&lt;p&gt;Instead of processing the information as completely new input again, it can read the cached information.&lt;/p&gt;

&lt;p&gt;That's:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache Read&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In simple terms:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Cache Read = using information that was already stored in the cache.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is usually cheaper than sending the same information as completely new input.&lt;/p&gt;

&lt;p&gt;For example, suppose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input = $2 / million tokens
Cache Read = $0.50 / million tokens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you repeatedly work with the same project context, using the cache can significantly reduce the cost of processing that repeated context.&lt;/p&gt;




&lt;h1&gt;
  
  
  5. A Simple Real-World Example
&lt;/h1&gt;

&lt;p&gt;Imagine you are building a Laravel application in Cursor.&lt;/p&gt;

&lt;p&gt;You ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Create an API endpoint for registering users."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cursor might send:&lt;/p&gt;

&lt;h3&gt;
  
  
  Input
&lt;/h3&gt;

&lt;p&gt;Your instructions + relevant Laravel code.&lt;/p&gt;

&lt;p&gt;Then the AI generates:&lt;/p&gt;

&lt;h3&gt;
  
  
  Output
&lt;/h3&gt;

&lt;p&gt;The controller, request validation, route and other code.&lt;/p&gt;

&lt;p&gt;Now you ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Add email verification."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Some of the project context may already be cached.&lt;/p&gt;

&lt;p&gt;So instead of treating everything as completely new information, Cursor may use:&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache Read
&lt;/h3&gt;

&lt;p&gt;to reuse previously processed context.&lt;/p&gt;

&lt;p&gt;If Cursor needs to add new information to the cache, that's:&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache Write
&lt;/h3&gt;




&lt;h1&gt;
  
  
  6. Why Cache Pricing Can Be Confusing
&lt;/h1&gt;

&lt;p&gt;You might see pricing like:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Price per million tokens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Input&lt;/td&gt;
&lt;td&gt;$2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache Write&lt;/td&gt;
&lt;td&gt;$2.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache Read&lt;/td&gt;
&lt;td&gt;$0.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output&lt;/td&gt;
&lt;td&gt;$10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At first, this can look confusing.&lt;/p&gt;

&lt;p&gt;You might ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Why am I being charged four different prices?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Because the AI provider is measuring &lt;strong&gt;different types of token processing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Think about it like a database:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Input&lt;/strong&gt; = sending data to the database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache Write&lt;/strong&gt; = storing frequently used data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache Read&lt;/strong&gt; = retrieving stored data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output&lt;/strong&gt; = generating the result&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The exact implementation of caching differs between AI providers, but this mental model is useful for understanding the pricing table.&lt;/p&gt;




&lt;h1&gt;
  
  
  7. Putting Everything Together
&lt;/h1&gt;

&lt;p&gt;Let's say you use a model with:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Input&lt;/td&gt;
&lt;td&gt;$2/M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache Write&lt;/td&gt;
&lt;td&gt;$2.50/M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache Read&lt;/td&gt;
&lt;td&gt;$0.20/M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output&lt;/td&gt;
&lt;td&gt;$10/M&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;During one interaction, suppose you use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;20,000 input tokens&lt;/li&gt;
&lt;li&gt;5,000 cache-write tokens&lt;/li&gt;
&lt;li&gt;50,000 cache-read tokens&lt;/li&gt;
&lt;li&gt;10,000 output tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The approximate cost would be:&lt;/p&gt;

&lt;h3&gt;
  
  
  Input
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;20,000 / 1,000,000 × $2
= $0.04
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cache Write
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5,000 / 1,000,000 × $2.50
= $0.0125
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cache Read
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;50,000 / 1,000,000 × $0.20
= $0.01
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Output
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10,000 / 1,000,000 × $10
= $0.10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Total
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$0.04 + $0.0125 + $0.01 + $0.10
= $0.1625
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So that interaction would cost approximately:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$0.16&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The important lesson is that &lt;strong&gt;you don't simply multiply the model's headline price by the number of times you use it&lt;/strong&gt;. The actual cost depends on how many tokens are processed in each category.&lt;/p&gt;




&lt;h1&gt;
  
  
  8. Which One Should You Pay Most Attention To?
&lt;/h1&gt;

&lt;p&gt;As a beginner, focus on these two first:&lt;/p&gt;

&lt;h3&gt;
  
  
  Input
&lt;/h3&gt;

&lt;p&gt;How much information are you asking the AI to process?&lt;/p&gt;

&lt;h3&gt;
  
  
  Output
&lt;/h3&gt;

&lt;p&gt;How much information is the AI generating?&lt;/p&gt;

&lt;p&gt;These are the easiest to understand.&lt;/p&gt;

&lt;p&gt;Then learn:&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache Read
&lt;/h3&gt;

&lt;p&gt;This can help reduce the cost of repeatedly processing the same context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache Write
&lt;/h3&gt;

&lt;p&gt;This is related to putting information into the cache so it can potentially be reused.&lt;/p&gt;




&lt;h1&gt;
  
  
  9. Why This Matters When Using Cursor
&lt;/h1&gt;

&lt;p&gt;This becomes especially important when you use &lt;strong&gt;Agent mode&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Suppose you ask Cursor to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Build a complete payment integration."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cursor may need to understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your existing application&lt;/li&gt;
&lt;li&gt;Your database&lt;/li&gt;
&lt;li&gt;Existing controllers&lt;/li&gt;
&lt;li&gt;Routes&lt;/li&gt;
&lt;li&gt;Models&lt;/li&gt;
&lt;li&gt;Configuration&lt;/li&gt;
&lt;li&gt;Payment code&lt;/li&gt;
&lt;li&gt;Documentation&lt;/li&gt;
&lt;li&gt;Your instructions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's potentially a lot of context.&lt;/p&gt;

&lt;p&gt;If you then continue asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Now add callbacks."&lt;/p&gt;

&lt;p&gt;"Now add transaction validation."&lt;/p&gt;

&lt;p&gt;"Now add retries."&lt;/p&gt;

&lt;p&gt;"Now write tests."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cursor may repeatedly work with a large amount of project context.&lt;/p&gt;

&lt;p&gt;This is one reason why &lt;strong&gt;Agent-heavy users can consume significantly more AI usage than developers who only use autocomplete or ask small questions.&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  10. The Easiest Way to Remember It
&lt;/h1&gt;

&lt;p&gt;Think of an AI conversation like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Input&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"Here is what I want and here is the information you need."&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache Write&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"Store some of this information so we can reuse it."&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache Read&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"Use information we already stored."&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"Here is the answer/code I generated."&lt;/p&gt;

&lt;p&gt;And because each operation can have a different computational cost, &lt;strong&gt;each can have a different price per million tokens&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is what the four columns in Cursor's model pricing table are telling you.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>beginners</category>
      <category>productivity</category>
      <category>python</category>
    </item>
    <item>
      <title>Cursor Models and Pricing</title>
      <dc:creator>Sospeter Mong'are</dc:creator>
      <pubDate>Mon, 24 Aug 2026 12:37:53 +0000</pubDate>
      <link>https://dev.to/msnmongare/cursor-mode-and-pricing-1j8c</link>
      <guid>https://dev.to/msnmongare/cursor-mode-and-pricing-1j8c</guid>
      <description>&lt;p&gt;If you are new to &lt;strong&gt;&lt;a href="https://cursor.com/docs/models-and-pricing" rel="noopener noreferrer"&gt;Cursor&lt;/a&gt;&lt;/strong&gt;, the pricing can look complicated because Cursor does not simply charge you based on "how many times you use AI."&lt;/p&gt;

&lt;p&gt;The cost depends mainly on &lt;strong&gt;which AI model you use, how much you use it, and which Cursor plan you are on&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Think of Cursor like a car rental service:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your &lt;strong&gt;Cursor plan&lt;/strong&gt; is your monthly membership.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI models&lt;/strong&gt; are different cars.&lt;/li&gt;
&lt;li&gt;Some cars are cheaper to drive, while others are more powerful and expensive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tokens&lt;/strong&gt; are the fuel you consume.&lt;/li&gt;
&lt;li&gt;When your included fuel runs out, you can either pay for more or upgrade your membership.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  1. What is Cursor Mode?
&lt;/h2&gt;

&lt;p&gt;Cursor allows you to use different AI models directly inside your code editor.&lt;/p&gt;

&lt;p&gt;For example, you can ask AI to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write code&lt;/li&gt;
&lt;li&gt;Explain code&lt;/li&gt;
&lt;li&gt;Fix bugs&lt;/li&gt;
&lt;li&gt;Refactor your application&lt;/li&gt;
&lt;li&gt;Create database queries&lt;/li&gt;
&lt;li&gt;Build features&lt;/li&gt;
&lt;li&gt;Analyze errors&lt;/li&gt;
&lt;li&gt;Work on multiple files&lt;/li&gt;
&lt;li&gt;Make changes to your project using Agent mode&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important thing is that &lt;strong&gt;different AI models have different capabilities and costs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Cursor groups these models into two main pools.&lt;/p&gt;




&lt;h1&gt;
  
  
  2. The Two Cursor Usage Pools
&lt;/h1&gt;

&lt;p&gt;Cursor currently has two separate usage pools:&lt;/p&gt;

&lt;h3&gt;
  
  
  Pool 1: Cursor Models
&lt;/h3&gt;

&lt;p&gt;This pool contains Cursor's own models, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cursor Grok 4.6&lt;/li&gt;
&lt;li&gt;Cursor Grok 4.5&lt;/li&gt;
&lt;li&gt;Composer 2.5&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These models come with &lt;strong&gt;generous included usage&lt;/strong&gt; depending on your plan.&lt;/p&gt;

&lt;p&gt;For most developers, this is the pool you will use regularly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pool 2: Other Models
&lt;/h3&gt;

&lt;p&gt;This pool contains models from companies such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OpenAI&lt;/li&gt;
&lt;li&gt;Anthropic&lt;/li&gt;
&lt;li&gt;Google&lt;/li&gt;
&lt;li&gt;Other providers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GPT-5.6 Luna&lt;/li&gt;
&lt;li&gt;GPT-5.6 Sol&lt;/li&gt;
&lt;li&gt;GPT-5.6 Terra&lt;/li&gt;
&lt;li&gt;Claude Sonnet 5&lt;/li&gt;
&lt;li&gt;Claude Opus 5&lt;/li&gt;
&lt;li&gt;Gemini 3.1 Pro&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These models consume your &lt;strong&gt;Other Models usage allowance&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  3. What Are Tokens?
&lt;/h1&gt;

&lt;p&gt;This is probably the most important concept to understand.&lt;/p&gt;

&lt;p&gt;AI models don't charge you simply because you sent a message.&lt;/p&gt;

&lt;p&gt;They measure usage in &lt;strong&gt;tokens&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A token is a small piece of text.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Create a Laravel API for user registration."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI needs to process your request, your existing code, and potentially the response it generates.&lt;/p&gt;

&lt;p&gt;This creates:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Input tokens + Output tokens = AI usage&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So if you give the AI a huge Laravel project and ask it to modify several files, it can consume significantly more tokens than asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What is dependency injection?"&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Understanding AI Model Pricing
&lt;/h1&gt;

&lt;p&gt;When you look at AI pricing, you will often see four numbers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pricing component&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Input&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;What you send to the AI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cache Write&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Information saved temporarily so it can be reused&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cache Read&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Previously cached information that the AI reads again&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Output&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;What the AI generates for you&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are usually priced &lt;strong&gt;per million tokens&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let's break them down.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Input
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Input is everything you give the AI.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This can include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your question&lt;/li&gt;
&lt;li&gt;Your instructions&lt;/li&gt;
&lt;li&gt;Code you provide&lt;/li&gt;
&lt;li&gt;Files you ask it to analyze&lt;/li&gt;
&lt;li&gt;Previous conversation/context&lt;/li&gt;
&lt;li&gt;Relevant parts of your project&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, you tell Cursor:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Fix this Laravel authentication bug."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And Cursor sends your instructions plus relevant code to the AI.&lt;/p&gt;

&lt;p&gt;That information is &lt;strong&gt;input&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;Suppose the model's input price is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$2 per million tokens&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your request uses 10,000 input tokens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10,000 / 1,000,000 × $2
= $0.02
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the input cost would be approximately &lt;strong&gt;$0.02&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  2. Output
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Output is what the AI generates in response.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For a coding assistant, output could include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code&lt;/li&gt;
&lt;li&gt;Explanations&lt;/li&gt;
&lt;li&gt;SQL queries&lt;/li&gt;
&lt;li&gt;Suggestions&lt;/li&gt;
&lt;li&gt;Refactored code&lt;/li&gt;
&lt;li&gt;JSON&lt;/li&gt;
&lt;li&gt;Documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Suppose a model costs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$6 per million output tokens&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If it generates 10,000 tokens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10,000 / 1,000,000 × $6
= $0.06
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the output cost would be approximately &lt;strong&gt;$0.06&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is Output often more expensive?
&lt;/h3&gt;

&lt;p&gt;Generating information is computationally expensive.&lt;/p&gt;

&lt;p&gt;That's why you will often see something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Input: $2/M&lt;br&gt;
Output: $6/M&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI charges differently for what it &lt;strong&gt;reads&lt;/strong&gt; and what it &lt;strong&gt;generates&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  3. Cache Write
&lt;/h1&gt;

&lt;p&gt;This one is slightly more complicated.&lt;/p&gt;

&lt;p&gt;Imagine you are working on a large Laravel project.&lt;/p&gt;

&lt;p&gt;Every time you ask Cursor something, Cursor may need to provide the AI with a lot of context about your project.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Laravel project
+ routes
+ controllers
+ models
+ migrations
+ services
+ configuration
+ existing code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sending the same information repeatedly can be inefficient.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caching allows some of that information to be stored temporarily so it can be reused.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When information is stored in the cache, that is called a:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache Write&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In simple terms:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Cache Write = putting information into the AI's reusable memory/cache.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  4. Cache Read
&lt;/h1&gt;

&lt;p&gt;Once information has been cached, the AI can reuse it.&lt;/p&gt;

&lt;p&gt;Instead of processing the information as completely new input again, it can read the cached information.&lt;/p&gt;

&lt;p&gt;That's:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache Read&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In simple terms:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Cache Read = using information that was already stored in the cache.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is usually cheaper than sending the same information as completely new input.&lt;/p&gt;

&lt;p&gt;For example, suppose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input = $2 / million tokens
Cache Read = $0.50 / million tokens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you repeatedly work with the same project context, using the cache can significantly reduce the cost of processing that repeated context.&lt;/p&gt;




&lt;h1&gt;
  
  
  5. A Simple Real-World Example
&lt;/h1&gt;

&lt;p&gt;Imagine you are building a Laravel application in Cursor.&lt;/p&gt;

&lt;p&gt;You ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Create an API endpoint for registering users."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cursor might send:&lt;/p&gt;

&lt;h3&gt;
  
  
  Input
&lt;/h3&gt;

&lt;p&gt;Your instructions + relevant Laravel code.&lt;/p&gt;

&lt;p&gt;Then the AI generates:&lt;/p&gt;

&lt;h3&gt;
  
  
  Output
&lt;/h3&gt;

&lt;p&gt;The controller, request validation, route and other code.&lt;/p&gt;

&lt;p&gt;Now you ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Add email verification."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Some of the project context may already be cached.&lt;/p&gt;

&lt;p&gt;So instead of treating everything as completely new information, Cursor may use:&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache Read
&lt;/h3&gt;

&lt;p&gt;to reuse previously processed context.&lt;/p&gt;

&lt;p&gt;If Cursor needs to add new information to the cache, that's:&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache Write
&lt;/h3&gt;




&lt;h1&gt;
  
  
  6. Why Cache Pricing Can Be Confusing
&lt;/h1&gt;

&lt;p&gt;You might see pricing like:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Price per million tokens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Input&lt;/td&gt;
&lt;td&gt;$2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache Write&lt;/td&gt;
&lt;td&gt;$2.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache Read&lt;/td&gt;
&lt;td&gt;$0.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output&lt;/td&gt;
&lt;td&gt;$10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At first, this can look confusing.&lt;/p&gt;

&lt;p&gt;You might ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Why am I being charged four different prices?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Because the AI provider is measuring &lt;strong&gt;different types of token processing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Think about it like a database:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Input&lt;/strong&gt; = sending data to the database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache Write&lt;/strong&gt; = storing frequently used data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache Read&lt;/strong&gt; = retrieving stored data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output&lt;/strong&gt; = generating the result&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The exact implementation of caching differs between AI providers, but this mental model is useful for understanding the pricing table.&lt;/p&gt;




&lt;h1&gt;
  
  
  7. Putting Everything Together
&lt;/h1&gt;

&lt;p&gt;Let's say you use a model with:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Input&lt;/td&gt;
&lt;td&gt;$2/M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache Write&lt;/td&gt;
&lt;td&gt;$2.50/M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache Read&lt;/td&gt;
&lt;td&gt;$0.20/M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output&lt;/td&gt;
&lt;td&gt;$10/M&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;During one interaction, suppose you use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;20,000 input tokens&lt;/li&gt;
&lt;li&gt;5,000 cache-write tokens&lt;/li&gt;
&lt;li&gt;50,000 cache-read tokens&lt;/li&gt;
&lt;li&gt;10,000 output tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The approximate cost would be:&lt;/p&gt;

&lt;h3&gt;
  
  
  Input
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;20,000 / 1,000,000 × $2
= $0.04
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cache Write
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5,000 / 1,000,000 × $2.50
= $0.0125
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cache Read
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;50,000 / 1,000,000 × $0.20
= $0.01
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Output
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10,000 / 1,000,000 × $10
= $0.10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Total
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$0.04 + $0.0125 + $0.01 + $0.10
= $0.1625
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So that interaction would cost approximately:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$0.16&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The important lesson is that &lt;strong&gt;you don't simply multiply the model's headline price by the number of times you use it&lt;/strong&gt;. The actual cost depends on how many tokens are processed in each category.&lt;/p&gt;




&lt;h1&gt;
  
  
  8. Which One Should You Pay Most Attention To?
&lt;/h1&gt;

&lt;p&gt;As a beginner, focus on these two first:&lt;/p&gt;

&lt;h3&gt;
  
  
  Input
&lt;/h3&gt;

&lt;p&gt;How much information are you asking the AI to process?&lt;/p&gt;

&lt;h3&gt;
  
  
  Output
&lt;/h3&gt;

&lt;p&gt;How much information is the AI generating?&lt;/p&gt;

&lt;p&gt;These are the easiest to understand.&lt;/p&gt;

&lt;p&gt;Then learn:&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache Read
&lt;/h3&gt;

&lt;p&gt;This can help reduce the cost of repeatedly processing the same context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache Write
&lt;/h3&gt;

&lt;p&gt;This is related to putting information into the cache so it can potentially be reused.&lt;/p&gt;




&lt;h1&gt;
  
  
  9. Why This Matters When Using Cursor
&lt;/h1&gt;

&lt;p&gt;This becomes especially important when you use &lt;strong&gt;Agent mode&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Suppose you ask Cursor to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Build a complete payment integration."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cursor may need to understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your existing application&lt;/li&gt;
&lt;li&gt;Your database&lt;/li&gt;
&lt;li&gt;Existing controllers&lt;/li&gt;
&lt;li&gt;Routes&lt;/li&gt;
&lt;li&gt;Models&lt;/li&gt;
&lt;li&gt;Configuration&lt;/li&gt;
&lt;li&gt;Payment code&lt;/li&gt;
&lt;li&gt;Documentation&lt;/li&gt;
&lt;li&gt;Your instructions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's potentially a lot of context.&lt;/p&gt;

&lt;p&gt;If you then continue asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Now add callbacks."&lt;/p&gt;

&lt;p&gt;"Now add transaction validation."&lt;/p&gt;

&lt;p&gt;"Now add retries."&lt;/p&gt;

&lt;p&gt;"Now write tests."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cursor may repeatedly work with a large amount of project context.&lt;/p&gt;

&lt;p&gt;This is one reason why &lt;strong&gt;Agent-heavy users can consume significantly more AI usage than developers who only use autocomplete or ask small questions.&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  10. The Easiest Way to Remember It
&lt;/h1&gt;

&lt;p&gt;Think of an AI conversation like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Input&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"Here is what I want and here is the information you need."&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache Write&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"Store some of this information so we can reuse it."&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache Read&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"Use information we already stored."&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"Here is the answer/code I generated."&lt;/p&gt;

&lt;p&gt;And because each operation can have a different computational cost, &lt;strong&gt;each can have a different price per million tokens&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is what the four columns in Cursor's model pricing table are telling you.&lt;/p&gt;




&lt;h1&gt;
  
  
  4. Why Do Different Models Cost Different Amounts?
&lt;/h1&gt;

&lt;p&gt;Different AI models have different capabilities and operating costs.&lt;/p&gt;

&lt;p&gt;For example, according to the pricing you provided:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Composer 2.5&lt;/td&gt;
&lt;td&gt;$0.50/M tokens&lt;/td&gt;
&lt;td&gt;$2.50/M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grok 4.6&lt;/td&gt;
&lt;td&gt;$2/M&lt;/td&gt;
&lt;td&gt;$6/M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.6 Luna&lt;/td&gt;
&lt;td&gt;$0.20/M&lt;/td&gt;
&lt;td&gt;$1.20/M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-5.6 Sol&lt;/td&gt;
&lt;td&gt;$4/M&lt;/td&gt;
&lt;td&gt;$20/M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Sonnet 5&lt;/td&gt;
&lt;td&gt;$2/M&lt;/td&gt;
&lt;td&gt;$10/M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Opus 5&lt;/td&gt;
&lt;td&gt;$5/M&lt;/td&gt;
&lt;td&gt;$25/M&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The prices are &lt;strong&gt;per million tokens&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So don't look at a price like "$5" and think:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Every time I use Claude, I pay $5."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;It means that processing &lt;strong&gt;one million tokens&lt;/strong&gt; at that rate would cost that amount.&lt;/p&gt;




&lt;h1&gt;
  
  
  5. Cursor Plans
&lt;/h1&gt;

&lt;p&gt;There are four main individual plans listed in the information you provided.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;Monthly price&lt;/th&gt;
&lt;th&gt;Other Models included&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Start&lt;/td&gt;
&lt;td&gt;₹649/month&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;$20/month&lt;/td&gt;
&lt;td&gt;$20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pro Plus&lt;/td&gt;
&lt;td&gt;$60/month&lt;/td&gt;
&lt;td&gt;$70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ultra&lt;/td&gt;
&lt;td&gt;$200/month&lt;/td&gt;
&lt;td&gt;$400&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The important difference is how much AI usage you get.&lt;/p&gt;




&lt;h1&gt;
  
  
  6. Cursor Pro - $20/month
&lt;/h1&gt;

&lt;p&gt;For most developers, &lt;strong&gt;Pro is the starting point worth considering&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You pay:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$20/month&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generous Cursor Models usage&lt;/li&gt;
&lt;li&gt;$20 of Other Models usage&lt;/li&gt;
&lt;li&gt;Unlimited Tab completions&lt;/li&gt;
&lt;li&gt;Extended Agent usage&lt;/li&gt;
&lt;li&gt;Bugbot&lt;/li&gt;
&lt;li&gt;Cloud Agents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, if you are building Laravel applications and regularly use Agent mode, Pro can be a reasonable starting point.&lt;/p&gt;




&lt;h1&gt;
  
  
  7. Pro Plus - $60/month
&lt;/h1&gt;

&lt;p&gt;Pro Plus costs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$60/month&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It includes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$70 of Other Models usage&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is useful if you are a heavier AI coding user.&lt;/p&gt;

&lt;p&gt;For example, you might use AI throughout the day for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Writing features&lt;/li&gt;
&lt;li&gt;Refactoring&lt;/li&gt;
&lt;li&gt;Debugging&lt;/li&gt;
&lt;li&gt;Writing tests&lt;/li&gt;
&lt;li&gt;Database work&lt;/li&gt;
&lt;li&gt;Documentation&lt;/li&gt;
&lt;li&gt;Code reviews&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you regularly use expensive third-party models, you can consume your Pro allowance relatively quickly.&lt;/p&gt;

&lt;p&gt;Pro Plus gives you a larger allowance.&lt;/p&gt;




&lt;h1&gt;
  
  
  8. Ultra - $200/month
&lt;/h1&gt;

&lt;p&gt;Ultra is designed for very heavy users.&lt;/p&gt;

&lt;p&gt;It costs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$200/month&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;and includes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$400 of Other Models usage&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is more suitable for power users who heavily rely on AI agents, automation and multiple models.&lt;/p&gt;

&lt;p&gt;For an average developer who occasionally asks AI to write code, Ultra is probably unnecessary.&lt;/p&gt;




&lt;h1&gt;
  
  
  9. What Happens When You Use Up Your Allowance?
&lt;/h1&gt;

&lt;p&gt;This is an important part of Cursor's pricing.&lt;/p&gt;

&lt;p&gt;Suppose you have Pro.&lt;/p&gt;

&lt;p&gt;You have:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$20 of Other Models usage&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You eventually consume the entire $20.&lt;/p&gt;

&lt;p&gt;You have two choices:&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1 - Add on-demand usage
&lt;/h3&gt;

&lt;p&gt;You can continue using the models and pay for additional usage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2 - Upgrade
&lt;/h3&gt;

&lt;p&gt;You can upgrade to a higher plan with a larger included allowance.&lt;/p&gt;

&lt;p&gt;Importantly, reaching your included usage does not necessarily mean:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Cursor stops working."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can continue through additional paid usage if your account allows it.&lt;/p&gt;




&lt;h1&gt;
  
  
  10. Why Does Model Selection Matter?
&lt;/h1&gt;

&lt;p&gt;Imagine you ask three different AI models to perform the same task.&lt;/p&gt;

&lt;p&gt;You might ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Build a complete Laravel authentication system."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One model might consume relatively little usage.&lt;/p&gt;

&lt;p&gt;Another might consume significantly more.&lt;/p&gt;

&lt;p&gt;A more powerful model can also generate more expensive output.&lt;/p&gt;

&lt;p&gt;Therefore:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your choice of model affects how quickly your usage allowance is consumed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is why Cursor gives you different models instead of forcing you to use one model.&lt;/p&gt;




&lt;h1&gt;
  
  
  11. Fast vs Normal Models
&lt;/h1&gt;

&lt;p&gt;You may also see models such as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Grok 4.6&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Grok 4.6 Fast&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Fast version is designed to provide faster responses, but according to the pricing you provided, it costs more per token.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Grok 4.6&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Input: $2/M&lt;br&gt;
Output: $6/M&lt;/p&gt;

&lt;p&gt;While:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Grok 4.6 Fast&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Input: $4/M&lt;br&gt;
Output: $12/M&lt;/p&gt;

&lt;p&gt;So you're effectively paying more for speed.&lt;/p&gt;

&lt;p&gt;A simple way to think about it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Normal = cheaper&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fast = more expensive, faster&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  12. What Is Auto?
&lt;/h1&gt;

&lt;p&gt;Cursor also has an &lt;strong&gt;Auto&lt;/strong&gt; feature.&lt;/p&gt;

&lt;p&gt;Instead of manually selecting a model, you can allow Cursor to decide which model to use.&lt;/p&gt;

&lt;p&gt;There are three Auto modes:&lt;/p&gt;

&lt;h3&gt;
  
  
  Auto Cost
&lt;/h3&gt;

&lt;p&gt;Optimizes for cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  Auto Balance
&lt;/h3&gt;

&lt;p&gt;Attempts to balance cost and intelligence.&lt;/p&gt;

&lt;h3&gt;
  
  
  Auto Intelligence
&lt;/h3&gt;

&lt;p&gt;Prioritizes intelligence/capability.&lt;/p&gt;

&lt;p&gt;This is useful for beginners because you don't always need to understand every model.&lt;/p&gt;

&lt;p&gt;You can essentially tell Cursor:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Choose the appropriate model for me."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  13. How Much Usage Do You Actually Need?
&lt;/h1&gt;

&lt;p&gt;This is where things become easier.&lt;/p&gt;

&lt;p&gt;Cursor's own guidance from the pricing information you provided is approximately:&lt;/p&gt;

&lt;h3&gt;
  
  
  Daily Tab users
&lt;/h3&gt;

&lt;p&gt;Usually stay within:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$20/month&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Limited Agent users
&lt;/h3&gt;

&lt;p&gt;Often stay within:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$20/month&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Daily Agent users
&lt;/h3&gt;

&lt;p&gt;Typically:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$60-$100/month&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Power users
&lt;/h3&gt;

&lt;p&gt;People using multiple agents and automation can reach:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$200+/month&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So your usage pattern matters more than simply saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I am a developer."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  14. What Does This Mean for a Developer?
&lt;/h1&gt;

&lt;p&gt;Imagine three developers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Developer A - Light AI user
&lt;/h3&gt;

&lt;p&gt;They mostly use autocomplete and occasionally ask AI questions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro is probably enough.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Developer B - Daily AI developer
&lt;/h3&gt;

&lt;p&gt;They use Agent every day to build features, debug applications and modify multiple files.&lt;/p&gt;

&lt;p&gt;They might consume:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$60-$100+ of usage per month.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pro Plus may make more sense.&lt;/p&gt;

&lt;h3&gt;
  
  
  Developer C - AI-first developer
&lt;/h3&gt;

&lt;p&gt;They spend most of their working day using AI agents and automation.&lt;/p&gt;

&lt;p&gt;They may consume:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$200+ per month.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ultra could make sense for them.&lt;/p&gt;




&lt;h1&gt;
  
  
  15. The Most Important Thing to Understand
&lt;/h1&gt;

&lt;p&gt;Don't think about Cursor pricing as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How many AI messages do I get?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Think about it as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"How much AI computation am I consuming?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A simple question such as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What does this function do?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;might consume very little.&lt;/p&gt;

&lt;p&gt;But this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Analyze my entire Laravel application, identify architectural problems, refactor the authentication system, update the tests and fix all related errors."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;can consume much more.&lt;/p&gt;

&lt;p&gt;The second request requires the AI to process much more context and generate much more output.&lt;/p&gt;




&lt;h1&gt;
  
  
  16. What I Would Recommend for a Developer
&lt;/h1&gt;

&lt;p&gt;If you're just getting started with Cursor:&lt;/p&gt;

&lt;h3&gt;
  
  
  Start with Pro - $20/month
&lt;/h3&gt;

&lt;p&gt;Use it normally for a month.&lt;/p&gt;

&lt;p&gt;Pay attention to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How frequently you use Agent&lt;/li&gt;
&lt;li&gt;Which models you use&lt;/li&gt;
&lt;li&gt;How quickly your usage allowance decreases&lt;/li&gt;
&lt;li&gt;How much of your work is being done through AI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you consistently reach your limits, then consider:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro Plus - $60/month&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're an extremely heavy AI user:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ultra - $200/month&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Don't immediately pay $200 just because Ultra has a much larger allowance.&lt;/p&gt;




&lt;h1&gt;
  
  
  17. The Simple Mental Model
&lt;/h1&gt;

&lt;p&gt;You can remember Cursor pricing with this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plan = Your monthly membership&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model = The AI you choose&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tokens = How much you use&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Usage pool = Your allowance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On-demand = Pay for additional usage&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fast = More expensive but faster&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auto = Let Cursor choose the model&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And perhaps the most important one:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The more AI work you give Cursor, the more tokens you consume.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So if you are using Cursor primarily for coding, the biggest factor is not just the subscription price. &lt;strong&gt;Your development workflow and how heavily you rely on Agent and expensive models will determine your actual monthly cost.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>cursor</category>
      <category>ai</category>
      <category>beginners</category>
      <category>productivity</category>
    </item>
    <item>
      <title>AI System Prompts for Businesses: From Chatbots to Digital Employees</title>
      <dc:creator>Sospeter Mong'are</dc:creator>
      <pubDate>Thu, 20 Aug 2026 03:32:00 +0000</pubDate>
      <link>https://dev.to/msnmongare/ai-system-prompts-for-businesses-from-chatbots-to-digital-employees-e00</link>
      <guid>https://dev.to/msnmongare/ai-system-prompts-for-businesses-from-chatbots-to-digital-employees-e00</guid>
      <description>&lt;p&gt;As businesses adopt AI, one of the most overlooked components is the &lt;strong&gt;system prompt&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Many organizations think of a prompt as simply an instruction such as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"You are a customer support assistant. Answer customer questions."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That may work for a simple chatbot, but it is not enough for a business-critical AI system.&lt;/p&gt;

&lt;p&gt;A production AI system needs to understand its role, its responsibilities, its boundaries, the information it can access, the actions it can perform, and when it should involve a human.&lt;/p&gt;

&lt;p&gt;In other words, a good system prompt is not just a question to an AI. It is closer to an &lt;strong&gt;operating policy for an AI employee&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is an AI system prompt?
&lt;/h2&gt;

&lt;p&gt;A system prompt defines how an AI should behave throughout an interaction.&lt;/p&gt;

&lt;p&gt;For a business, it can establish things such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What the AI is responsible for&lt;/li&gt;
&lt;li&gt;What it is allowed to do&lt;/li&gt;
&lt;li&gt;What it must never do&lt;/li&gt;
&lt;li&gt;What information it can use&lt;/li&gt;
&lt;li&gt;How it should make decisions&lt;/li&gt;
&lt;li&gt;When it should ask for clarification&lt;/li&gt;
&lt;li&gt;When it should escalate to a human&lt;/li&gt;
&lt;li&gt;How it should handle sensitive information&lt;/li&gt;
&lt;li&gt;How it should respond when a tool fails&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, a customer support AI should not simply be told to "help customers."&lt;/p&gt;

&lt;p&gt;It should know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which products it supports&lt;/li&gt;
&lt;li&gt;Which policies it can reference&lt;/li&gt;
&lt;li&gt;What refunds it can authorize&lt;/li&gt;
&lt;li&gt;What customer information it can access&lt;/li&gt;
&lt;li&gt;Which actions require approval&lt;/li&gt;
&lt;li&gt;What happens when it cannot find an answer&lt;/li&gt;
&lt;li&gt;When a human agent must take over&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where system prompts become particularly valuable in business.&lt;/p&gt;

&lt;h2&gt;
  
  
  A business AI needs boundaries
&lt;/h2&gt;

&lt;p&gt;One of the biggest mistakes businesses can make is focusing only on what an AI can do.&lt;/p&gt;

&lt;p&gt;The more important question is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What should the AI not do?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine a finance AI connected to a company's accounting system.&lt;/p&gt;

&lt;p&gt;If the AI can access transactions, generate reports, and initiate financial actions, simply telling it to "help the finance team" is dangerous.&lt;/p&gt;

&lt;p&gt;The system needs explicit boundaries.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It can analyze transactions.&lt;/li&gt;
&lt;li&gt;It can generate reports.&lt;/li&gt;
&lt;li&gt;It can identify suspicious transactions.&lt;/li&gt;
&lt;li&gt;It cannot approve payments.&lt;/li&gt;
&lt;li&gt;It cannot change financial records without authorization.&lt;/li&gt;
&lt;li&gt;It cannot invent financial figures.&lt;/li&gt;
&lt;li&gt;It must escalate unusual transactions to a human.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AI's capabilities should therefore be matched with clearly defined authority.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical structure for business system prompts
&lt;/h2&gt;

&lt;p&gt;A useful business system prompt can contain several sections.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Role
&lt;/h3&gt;

&lt;p&gt;Define who the AI is.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"You are the Customer Support AI for ABC Insurance."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This gives the model a clear operational identity.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Objective
&lt;/h3&gt;

&lt;p&gt;Define what success looks like.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Your primary responsibility is to resolve customer questions accurately while minimizing unnecessary escalation to human agents."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is better than simply saying "help customers."&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Scope
&lt;/h3&gt;

&lt;p&gt;Define what the AI is responsible for.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Answer product questions&lt;/li&gt;
&lt;li&gt;Explain policies&lt;/li&gt;
&lt;li&gt;Check claim status&lt;/li&gt;
&lt;li&gt;Create support tickets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And explicitly define what is outside its scope.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Rules
&lt;/h3&gt;

&lt;p&gt;These are the business policies the AI must follow.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Never invent information.&lt;/li&gt;
&lt;li&gt;Only provide information from approved sources.&lt;/li&gt;
&lt;li&gt;Do not expose confidential information.&lt;/li&gt;
&lt;li&gt;Do not make unauthorized decisions.&lt;/li&gt;
&lt;li&gt;Do not claim an action succeeded unless the system confirms it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Data handling
&lt;/h3&gt;

&lt;p&gt;Businesses need to think carefully about what information an AI can access.&lt;/p&gt;

&lt;p&gt;A system prompt can establish rules such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only request information necessary for the task.&lt;/li&gt;
&lt;li&gt;Treat customer information as confidential.&lt;/li&gt;
&lt;li&gt;Never request passwords or authentication codes.&lt;/li&gt;
&lt;li&gt;Never expose internal system information.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, the prompt should not be the only security mechanism. Permissions and access controls should exist outside the prompt as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Tool usage
&lt;/h3&gt;

&lt;p&gt;Modern AI systems increasingly interact with tools.&lt;/p&gt;

&lt;p&gt;An AI might have access to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CRM systems&lt;/li&gt;
&lt;li&gt;Databases&lt;/li&gt;
&lt;li&gt;Payment APIs&lt;/li&gt;
&lt;li&gt;Email&lt;/li&gt;
&lt;li&gt;Ticketing systems&lt;/li&gt;
&lt;li&gt;ERP systems&lt;/li&gt;
&lt;li&gt;Internal APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The prompt should explain how those tools should be used.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Before modifying customer information, verify the customer's identity and confirm that the requested operation is authorized."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The actual authorization should still be enforced by the underlying system.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Escalation
&lt;/h3&gt;

&lt;p&gt;This is one of the most important parts.&lt;/p&gt;

&lt;p&gt;The AI should know when &lt;strong&gt;not&lt;/strong&gt; to act.&lt;/p&gt;

&lt;p&gt;For example, a customer support AI should escalate when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The customer explicitly requests a human.&lt;/li&gt;
&lt;li&gt;The issue involves a disputed financial transaction.&lt;/li&gt;
&lt;li&gt;A refund exceeds its authorization limit.&lt;/li&gt;
&lt;li&gt;There is a potential security incident.&lt;/li&gt;
&lt;li&gt;The customer threatens legal action.&lt;/li&gt;
&lt;li&gt;The AI cannot find sufficient information.&lt;/li&gt;
&lt;li&gt;A required system is unavailable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A mature AI system understands that &lt;strong&gt;escalation is sometimes a successful outcome&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Failure handling
&lt;/h3&gt;

&lt;p&gt;What happens when the AI's tools fail?&lt;/p&gt;

&lt;p&gt;Suppose an AI calls a payment API and the API times out.&lt;/p&gt;

&lt;p&gt;The AI should not tell the customer:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Your payment was successfully processed."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead, it should know that the operation could not be confirmed.&lt;/p&gt;

&lt;p&gt;This is a critical principle for AI automation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Never confuse an attempted action with a successful action.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The system should verify outcomes before reporting success.&lt;/p&gt;

&lt;h2&gt;
  
  
  Different businesses can build different AI employees
&lt;/h2&gt;

&lt;p&gt;The same architecture can support many business functions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Customer Support AI
&lt;/h3&gt;

&lt;p&gt;Responsible for resolving customer questions and support requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sales AI
&lt;/h3&gt;

&lt;p&gt;Can qualify leads, answer product questions, recommend products, and schedule meetings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finance AI
&lt;/h3&gt;

&lt;p&gt;Can analyze transactions, generate reports, identify anomalies, and assist finance teams.&lt;/p&gt;

&lt;h3&gt;
  
  
  HR AI
&lt;/h3&gt;

&lt;p&gt;Can answer questions about company policies, leave procedures, benefits, and onboarding.&lt;/p&gt;

&lt;h3&gt;
  
  
  IT Helpdesk AI
&lt;/h3&gt;

&lt;p&gt;Can troubleshoot common technical issues, create tickets, and guide employees through approved procedures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Analyst AI
&lt;/h3&gt;

&lt;p&gt;Can answer business questions using approved datasets and generate analytical summaries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Operations AI
&lt;/h3&gt;

&lt;p&gt;Can monitor workflows, identify failures, and notify responsible teams.&lt;/p&gt;

&lt;h3&gt;
  
  
  Document Processing AI
&lt;/h3&gt;

&lt;p&gt;Can extract information from documents, validate fields, classify documents, and route exceptions.&lt;/p&gt;

&lt;p&gt;The key is that each AI needs a &lt;strong&gt;specific responsibility and authority model&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  But a system prompt is not the AI system
&lt;/h2&gt;

&lt;p&gt;This is perhaps the most important distinction.&lt;/p&gt;

&lt;p&gt;A company can write an excellent system prompt and still build a terrible AI system.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because a production AI system is much more than a prompt.&lt;/p&gt;

&lt;p&gt;A useful mental model is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI Business System = Prompt + Knowledge + Tools + Permissions + Workflow + Monitoring + Human Escalation&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompt
&lt;/h3&gt;

&lt;p&gt;Defines how the AI should behave.&lt;/p&gt;

&lt;h3&gt;
  
  
  Knowledge
&lt;/h3&gt;

&lt;p&gt;Provides trusted information.&lt;/p&gt;

&lt;p&gt;This could include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Company policies&lt;/li&gt;
&lt;li&gt;Product documentation&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;li&gt;Contracts&lt;/li&gt;
&lt;li&gt;Procedures&lt;/li&gt;
&lt;li&gt;Internal documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where technologies such as &lt;strong&gt;RAG&lt;/strong&gt; become useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tools
&lt;/h3&gt;

&lt;p&gt;Allow the AI to interact with business systems.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI
 |
 +-- CRM
 +-- Database
 +-- Payment API
 +-- Email
 +-- Ticketing System
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Permissions
&lt;/h3&gt;

&lt;p&gt;Determine what the AI is actually allowed to do.&lt;/p&gt;

&lt;p&gt;The prompt can say:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"You cannot approve payments."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But the stronger control is for the payment system itself to reject unauthorized payment approval requests.&lt;/p&gt;

&lt;p&gt;This leads to an important engineering principle:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not rely on prompts for security.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Prompts provide behavioral guidance. Applications, APIs, identity systems, and databases should enforce security.&lt;/p&gt;

&lt;h3&gt;
  
  
  Workflow
&lt;/h3&gt;

&lt;p&gt;Determines when the AI runs and what happens before and after it runs.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer request
       |
       v
AI analyzes request
       |
       +---- Simple question ---&amp;gt; Answer
       |
       +---- Support issue -----&amp;gt; Create ticket
       |
       +---- Sensitive action --&amp;gt; Human approval
       |
       +---- Unknown ----------&amp;gt; Escalate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where AI moves from being a chatbot to becoming part of a business process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitoring
&lt;/h3&gt;

&lt;p&gt;Businesses also need to know when their AI is failing.&lt;/p&gt;

&lt;p&gt;They should monitor things such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Failed tool calls&lt;/li&gt;
&lt;li&gt;Escalation rates&lt;/li&gt;
&lt;li&gt;Incorrect responses&lt;/li&gt;
&lt;li&gt;Response latency&lt;/li&gt;
&lt;li&gt;Token usage&lt;/li&gt;
&lt;li&gt;Business outcomes&lt;/li&gt;
&lt;li&gt;API failures&lt;/li&gt;
&lt;li&gt;Human overrides&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates an important question for every AI automation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens when it breaks, and who knows first?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The three questions before automating AI
&lt;/h2&gt;

&lt;p&gt;Before building an AI workflow, businesses should ask three simple questions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who owns it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Someone must be responsible for the AI system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What does good look like?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Define measurable success.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;70% of common support requests resolved automatically&lt;/li&gt;
&lt;li&gt;Less than 2% incorrect responses&lt;/li&gt;
&lt;li&gt;All financial actions require approval&lt;/li&gt;
&lt;li&gt;Average response time below 10 seconds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What happens when it breaks, and who knows first?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There should be an escalation path, monitoring, alerting, and an accountable person or team.&lt;/p&gt;

&lt;p&gt;These questions apply to both traditional automation and AI automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The future is not "AI everywhere"
&lt;/h2&gt;

&lt;p&gt;Businesses should not automate a process simply because AI can automate it.&lt;/p&gt;

&lt;p&gt;The better question is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where does AI create measurable business value without introducing unacceptable risk?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A customer support AI might make sense because thousands of repetitive questions can be handled automatically.&lt;/p&gt;

&lt;p&gt;An AI approving million-shilling payments without human oversight might not.&lt;/p&gt;

&lt;p&gt;The difference is not the AI model.&lt;/p&gt;

&lt;p&gt;The difference is &lt;strong&gt;system design&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;The next stage of AI adoption in businesses will not simply be about giving employees access to ChatGPT.&lt;/p&gt;

&lt;p&gt;It will be about building &lt;strong&gt;AI systems that operate inside real business processes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;These systems will have roles, permissions, knowledge, tools, workflows, monitoring, and escalation mechanisms.&lt;/p&gt;

&lt;p&gt;And the system prompt will become one of the components that defines how these AI employees operate.&lt;/p&gt;

&lt;p&gt;But businesses should remember:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A prompt can tell an AI what it should do. A well-engineered system determines what it can actually do.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That distinction is what separates an AI demo from a production AI system.&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>powerplatform</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How to Set Up a VPS Server From Scratch (Complete Beginner's Guide)</title>
      <dc:creator>Sospeter Mong'are</dc:creator>
      <pubDate>Wed, 19 Aug 2026 07:25:30 +0000</pubDate>
      <link>https://dev.to/msnmongare/how-to-set-up-a-vps-server-from-scratch-complete-beginners-guide-355j</link>
      <guid>https://dev.to/msnmongare/how-to-set-up-a-vps-server-from-scratch-complete-beginners-guide-355j</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;You just bought a VPS (Virtual Private Server). You have an IP address, a root password, and a blank Ubuntu server staring back at you. What do you do next?&lt;/p&gt;

&lt;p&gt;This is the question most beginners struggle with. Tutorials online tend to jump straight into deploying applications without covering the foundational work that every production server needs before a single line of application code is deployed.&lt;/p&gt;

&lt;p&gt;This guide covers everything you need to do after buying a VPS, in the correct order. By the end you will have a secure, hardened, production-ready server capable of hosting PHP/Laravel applications, Node.js APIs, Python apps, multiple domains, SSL certificates, and databases.&lt;/p&gt;

&lt;p&gt;Everything in this guide is based on a real Ubuntu 24.04 LTS server setup. The same principles apply to any Ubuntu or Debian-based VPS from providers like Contabo, DigitalOcean, Linode, or Vultr.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is a VPS and Why Does the Setup Matter?
&lt;/h2&gt;

&lt;p&gt;A VPS is a virtual machine running on shared physical hardware in a data centre. Unlike shared hosting where everything is managed for you, a VPS gives you full root access to a Linux server. You control everything.&lt;/p&gt;

&lt;p&gt;That freedom comes with responsibility. A freshly provisioned VPS is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accessible to the entire internet on port 22 (SSH)&lt;/li&gt;
&lt;li&gt;Running as root by default&lt;/li&gt;
&lt;li&gt;Accepting password-based logins&lt;/li&gt;
&lt;li&gt;Already being scanned by bots within minutes of going live&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you skip the setup and jump straight to deploying your application, you are building on an insecure foundation. A poorly configured VPS can be compromised within hours of going online.&lt;/p&gt;

&lt;p&gt;The setup process is not optional. It is the difference between a server that gets hacked and a server that does not.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 1: Inspect Your Server Before Touching Anything
&lt;/h2&gt;

&lt;p&gt;The first rule of working on any server is to understand what you have before you change anything. When you first log in, run a series of inspection commands to build a picture of your server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connect via SSH
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh root@YOUR_VPS_IP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your VPS provider will give you the root password in a welcome email. Once connected you will see a prompt like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;root@vps-hostname:~#&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You are now logged in as root on your server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check the Operating System
&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;cat&lt;/span&gt; /etc/os-release &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells you the exact OS version and kernel. Everything else depends on this. Ubuntu 24.04 LTS is the recommended choice for production servers as it is supported until April 2029.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check CPU, RAM, and Disk
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lscpu | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"^CPU&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="s2"&gt;s&lt;/span&gt;&lt;span class="se"&gt;\)&lt;/span&gt;&lt;span class="s2"&gt;|^Model name|^Thread|^Core"&lt;/span&gt;
free &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note how many CPU cores you have, how much RAM is available, and how much disk space you are working with. These numbers affect how you configure PHP workers, database buffers, and swap space later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check What Is Already Running
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ss &lt;span class="nt"&gt;-tlnp&lt;/span&gt;
systemctl list-units &lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;service &lt;span class="nt"&gt;--state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;running
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a clean VPS from a reputable provider you should see almost nothing running except SSH. If you see MySQL, Redis, or a web server already running, note it before proceeding.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check the Firewall
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ufw status verbose
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On most fresh Ubuntu VPS instances the firewall is installed but inactive. This means the only thing protecting your server right now is that nothing unexpected is listening. We will fix this shortly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check SSH Configuration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sshd &lt;span class="nt"&gt;-T&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"^permitrootlogin|^passwordauthentication|^pubkeyauthentication|^port"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a fresh VPS you will typically see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ssh"&gt;&lt;code&gt;&lt;span class="k"&gt;permitrootlogin&lt;/span&gt; &lt;span class="no"&gt;yes&lt;/span&gt;
&lt;span class="k"&gt;passwordauthentication&lt;/span&gt; &lt;span class="no"&gt;yes&lt;/span&gt;
&lt;span class="k"&gt;pubkeyauthentication&lt;/span&gt; &lt;span class="no"&gt;yes&lt;/span&gt;
&lt;span class="k"&gt;port&lt;/span&gt; &lt;span class="m"&gt;22&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means root can log in with a password over the public internet. This is the most dangerous default configuration on any server and the first thing we will fix.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 2: Secure SSH Access
&lt;/h2&gt;

&lt;p&gt;SSH (Secure Shell) is the gateway to your server. Securing it is the single most important step in this entire guide.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Root Login Is Dangerous
&lt;/h3&gt;

&lt;p&gt;When you log in as root, every command you run has unlimited power. A typo, a mistake, or a compromised session can destroy your entire server instantly. Worse, root is a known username that attackers target specifically. Brute force bots attempt thousands of root password combinations every hour against any server with port 22 open.&lt;/p&gt;

&lt;p&gt;The solution is to create a separate administrative user that uses SSH keys instead of passwords.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a Non-Root Admin User
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adduser yourname
usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;yourname
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Choose a username that is not obvious. Avoid &lt;code&gt;admin&lt;/code&gt;, &lt;code&gt;ubuntu&lt;/code&gt;, or &lt;code&gt;user&lt;/code&gt; as these are common brute force targets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set Up SSH Key Authentication
&lt;/h3&gt;

&lt;p&gt;SSH keys are cryptographic key pairs. Your private key stays on your local machine. Your public key is installed on the server. When you connect, SSH verifies the keys match without ever transmitting a password.&lt;/p&gt;

&lt;p&gt;On your local machine, check if you already have a key:&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;ls&lt;/span&gt; ~/.ssh/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you see &lt;code&gt;id_rsa&lt;/code&gt; and &lt;code&gt;id_rsa.pub&lt;/code&gt; or &lt;code&gt;id_ed25519&lt;/code&gt; and &lt;code&gt;id_ed25519.pub&lt;/code&gt; you already have a key pair. If not, generate one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-keygen &lt;span class="nt"&gt;-t&lt;/span&gt; ed25519 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"your-email@example.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ed25519 is the modern recommended key type. It is faster and more secure than the older RSA format.&lt;/p&gt;

&lt;p&gt;View your public key:&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;cat&lt;/span&gt; ~/.ssh/id_ed25519.pub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output is a long string starting with &lt;code&gt;ssh-ed25519&lt;/code&gt;. This is safe to share openly.&lt;/p&gt;

&lt;p&gt;Install the public key on the server by running this as root on the VPS:&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;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /home/yourname/.ssh
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"YOUR_PUBLIC_KEY_HERE"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /home/yourname/.ssh/authorized_keys
&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; yourname:yourname /home/yourname/.ssh
&lt;span class="nb"&gt;chmod &lt;/span&gt;700 /home/yourname/.ssh
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 /home/yourname/.ssh/authorized_keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The permissions are critical. SSH will refuse to use the key if the permissions are too open. &lt;code&gt;700&lt;/code&gt; on the directory means only the owner can access it. &lt;code&gt;600&lt;/code&gt; on the file means only the owner can read it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test the New User Before Changing Anything
&lt;/h3&gt;

&lt;p&gt;Open a second SSH session and test logging in as your new user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh yourname@YOUR_VPS_IP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should be logged in without entering a password. Then verify sudo works:&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 whoami&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It should return &lt;code&gt;root&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Do not close your original root session until this test passes. This is your safety net.&lt;/p&gt;

&lt;h3&gt;
  
  
  Harden the SSH Configuration
&lt;/h3&gt;

&lt;p&gt;Only after confirming your new user works, back up and edit the SSH configuration:&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; /etc/ssh/sshd_config /etc/ssh/sshd_config.backup
&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/ssh/sshd_config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change these settings:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Change to&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PermitRootLogin yes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PermitRootLogin no&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PasswordAuthentication yes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PasswordAuthentication no&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;X11Forwarding yes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;X11Forwarding no&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;#MaxAuthTries 6&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MaxAuthTries 3&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;#LoginGraceTime 2m&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;LoginGraceTime 30&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On Ubuntu 24.04, also check the override files in &lt;code&gt;/etc/ssh/sshd_config.d/&lt;/code&gt;. Files in this directory can override settings in the main config. Make sure &lt;code&gt;PasswordAuthentication no&lt;/code&gt; is consistent across all files in that directory.&lt;/p&gt;

&lt;p&gt;Test the configuration before applying it:&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;sshd &lt;span class="nt"&gt;-t&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Silence means the configuration is valid. Then reload:&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;systemctl reload ssh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test root login from a new terminal. You should see &lt;code&gt;Permission denied (publickey)&lt;/code&gt;. Root is now blocked.&lt;/p&gt;

&lt;h3&gt;
  
  
  What If You Get Locked Out?
&lt;/h3&gt;

&lt;p&gt;Every major VPS provider offers a web-based console in their control panel. This gives you direct access to the server regardless of SSH configuration. If you ever lock yourself out, log into your provider's control panel and use the console to fix the SSH config.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 3: Configure the Firewall
&lt;/h2&gt;

&lt;p&gt;Ubuntu ships with UFW (Uncomplicated Firewall). It is inactive by default. Configure and enable it before installing anything else.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why You Need a Firewall
&lt;/h3&gt;

&lt;p&gt;As you install software, some services will listen on public ports by default. MySQL, Redis, and PostgreSQL have all been compromised on servers where they were accidentally left exposed. A firewall provides a second layer of protection even when a service is misconfigured.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set Default Policies
&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;ufw default deny incoming
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw default allow outgoing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deny everything coming in, allow everything going out. Then explicitly allow only what needs to be public.&lt;/p&gt;

&lt;h3&gt;
  
  
  Allow Only What Is Necessary
&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;ufw allow ssh
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow http
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow https
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three rules. SSH for administration, HTTP for web traffic and SSL certificate validation, HTTPS for encrypted web traffic.&lt;/p&gt;

&lt;p&gt;Never open these ports publicly:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Reason to keep it closed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;3306&lt;/td&gt;
&lt;td&gt;MySQL&lt;/td&gt;
&lt;td&gt;No application needs direct public database access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5432&lt;/td&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;Same reason&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6379&lt;/td&gt;
&lt;td&gt;Redis&lt;/td&gt;
&lt;td&gt;Redis with no password and public access leads to immediate compromise&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3000&lt;/td&gt;
&lt;td&gt;Node.js&lt;/td&gt;
&lt;td&gt;App servers go behind Nginx, never directly public&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8000&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;Same reason&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Enable the Firewall
&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;ufw &lt;span class="nb"&gt;enable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the rules:&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;ufw status verbose
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see only ports 22, 80, and 443 allowed. Everything else is blocked.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 4: Harden the Server
&lt;/h2&gt;

&lt;p&gt;With SSH secured and the firewall active, harden the server itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add a Swap File
&lt;/h3&gt;

&lt;p&gt;A swap file is disk space used as overflow when RAM fills up. Without it, the Linux OOM (Out of Memory) killer will terminate processes randomly when memory runs out. On a server this usually means your database or web server gets killed unexpectedly.&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;fallocate &lt;span class="nt"&gt;-l&lt;/span&gt; 2G /swapfile
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;600 /swapfile
&lt;span class="nb"&gt;sudo &lt;/span&gt;mkswap /swapfile
&lt;span class="nb"&gt;sudo &lt;/span&gt;swapon /swapfile
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'/swapfile none swap sw 0 0'&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/fstab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last command makes swap permanent across reboots. Also reduce swappiness so the kernel prefers RAM over swap:&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;echo&lt;/span&gt; &lt;span class="s1"&gt;'vm.swappiness=10'&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/sysctl.conf
&lt;span class="nb"&gt;sudo &lt;/span&gt;sysctl vm.swappiness&lt;span class="o"&gt;=&lt;/span&gt;10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Set a Meaningful Hostname
&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;hostnamectl set-hostname your-server-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then update the hosts file:&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;nano /etc/hosts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Find the line with your old hostname and replace it with the new one. A clear hostname like &lt;code&gt;production-01&lt;/code&gt; or &lt;code&gt;app-server&lt;/code&gt; makes logs easier to read, especially when managing multiple servers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enable Automatic Security Updates
&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;apt &lt;span class="nb"&gt;install &lt;/span&gt;unattended-upgrades &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;dpkg-reconfigure &lt;span class="nt"&gt;--priority&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;low unattended-upgrades
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Select Yes when prompted. This automatically installs security patches without requiring manual intervention. It applies security updates only, not major version upgrades, so it will not break your applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Disable Unnecessary Services
&lt;/h3&gt;

&lt;p&gt;Ubuntu Server ships with services that serve no purpose on a VPS:&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;systemctl stop ModemManager multipathd udisks2
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl disable ModemManager multipathd udisks2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ModemManager handles mobile modems. multipathd manages multipath storage for enterprise SAN systems. udisks2 manages removable drives. None of these belong on a web server. Removing them reduces memory usage and attack surface.&lt;/p&gt;

&lt;h3&gt;
  
  
  Apply Kernel Hardening
&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;nano /etc/sysctl.d/99-hardening.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add these settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# IP Spoofing protection
&lt;/span&gt;&lt;span class="py"&gt;net.ipv4.conf.all.rp_filter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="py"&gt;net.ipv4.conf.default.rp_filter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1&lt;/span&gt;

&lt;span class="c"&gt;# Ignore ICMP redirects
&lt;/span&gt;&lt;span class="py"&gt;net.ipv4.conf.all.accept_redirects&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;0&lt;/span&gt;
&lt;span class="py"&gt;net.ipv6.conf.all.accept_redirects&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;0&lt;/span&gt;

&lt;span class="c"&gt;# Block SYN flood attacks
&lt;/span&gt;&lt;span class="py"&gt;net.ipv4.tcp_syncookies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="py"&gt;net.ipv4.tcp_max_syn_backlog&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;2048&lt;/span&gt;

&lt;span class="c"&gt;# Ignore ICMP broadcast requests
&lt;/span&gt;&lt;span class="py"&gt;net.ipv4.icmp_echo_ignore_broadcasts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1&lt;/span&gt;

&lt;span class="c"&gt;# Hide kernel pointers
&lt;/span&gt;&lt;span class="py"&gt;kernel.kptr_restrict&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply immediately:&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;sysctl &lt;span class="nt"&gt;-p&lt;/span&gt; /etc/sysctl.d/99-hardening.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These settings protect against common network attacks including IP spoofing, SYN floods, and ICMP redirect attacks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 5: Install Nginx as Your Reverse Proxy
&lt;/h2&gt;

&lt;p&gt;Nginx sits in front of all your applications. The internet talks to Nginx. Nginx decides where to send each request.&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;apt &lt;span class="nb"&gt;install &lt;/span&gt;nginx &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Nginx Architecture
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Internet
    |
    v
Nginx (port 80/443)
    |
    +-- example.com        --&amp;gt; PHP-FPM (Laravel)
    |
    +-- api.example.com    --&amp;gt; Node.js on port 3000
    |
    +-- app.example.com    --&amp;gt; Python on port 8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your applications never talk to the internet directly. Only Nginx does. This gives you SSL in one place, security headers in one place, and the ability to run many applications on a single server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Harden the Default Nginx Configuration
&lt;/h3&gt;

&lt;p&gt;Open the main config:&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;nano /etc/nginx/nginx.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the &lt;code&gt;http {}&lt;/code&gt; block, make these changes:&lt;/p&gt;

&lt;p&gt;Hide the Nginx version number from response headers:&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;server_tokens&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable gzip compression:&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;gzip&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;gzip_vary&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;gzip_min_length&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;gzip_types&lt;/span&gt; &lt;span class="nc"&gt;text/plain&lt;/span&gt; &lt;span class="nc"&gt;text/css&lt;/span&gt; &lt;span class="nc"&gt;application/json&lt;/span&gt; &lt;span class="nc"&gt;application/javascript&lt;/span&gt; &lt;span class="nc"&gt;text/xml&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set sensible timeouts and upload limits:&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;client_max_body_size&lt;/span&gt; &lt;span class="mi"&gt;64M&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;client_body_timeout&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;keepalive_timeout&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;send_timeout&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Disable the Default Site
&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 rm&lt;/span&gt; /etc/nginx/sites-enabled/default
&lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reload nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create Your Application Directories
&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 mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /var/www/example.com/public
&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; youruser:youruser /var/www/example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create a Server Block for Each Site
&lt;/h3&gt;

&lt;p&gt;For a Laravel application:&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;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;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="s"&gt;[::]:80&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;example.com&lt;/span&gt; &lt;span class="s"&gt;www.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="n"&gt;/var/www/example.com/current/public&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;X-Frame-Options&lt;/span&gt; &lt;span class="s"&gt;"SAMEORIGIN"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;X-Content-Type-Options&lt;/span&gt; &lt;span class="s"&gt;"nosniff"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;X-XSS-Protection&lt;/span&gt; &lt;span class="s"&gt;"1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kn"&gt;mode=block"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;index&lt;/span&gt; &lt;span class="s"&gt;index.php&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;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="n"&gt;/index.php?&lt;/span&gt;&lt;span class="nv"&gt;$query_string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt; &lt;span class="sr"&gt;\.php$&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_pass&lt;/span&gt; &lt;span class="s"&gt;unix:/var/run/php/php8.3-fpm.sock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_param&lt;/span&gt; &lt;span class="s"&gt;SCRIPT_FILENAME&lt;/span&gt; &lt;span class="nv"&gt;$realpath_root$fastcgi_script_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="s"&gt;fastcgi_params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt; &lt;span class="sr"&gt;/\.(?!well-known).*&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;deny&lt;/span&gt; &lt;span class="s"&gt;all&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="n"&gt;/var/log/nginx/example.com.access.log&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;error_log&lt;/span&gt;  &lt;span class="n"&gt;/var/log/nginx/example.com.error.log&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;p&gt;For a Node.js API:&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;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;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="s"&gt;[::]:80&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;api.example.com&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://localhost:3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_http_version&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-For&lt;/span&gt; &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span class="nv"&gt;$scheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="n"&gt;/var/log/nginx/api.example.com.access.log&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;error_log&lt;/span&gt;  &lt;span class="n"&gt;/var/log/nginx/api.example.com.error.log&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;p&gt;Enable each site and test:&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 ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
&lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reload nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Always run &lt;code&gt;nginx -t&lt;/code&gt; before reloading. If the test fails, Nginx keeps the old working configuration running.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 6: Install Your Application Stack
&lt;/h2&gt;

&lt;h3&gt;
  
  
  PHP 8.3 and Composer
&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;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; php8.3 php8.3-fpm php8.3-cli &lt;span class="se"&gt;\&lt;/span&gt;
php8.3-mysql php8.3-pgsql php8.3-mbstring php8.3-xml &lt;span class="se"&gt;\&lt;/span&gt;
php8.3-curl php8.3-zip php8.3-bcmath php8.3-opcache &lt;span class="se"&gt;\&lt;/span&gt;
php8.3-intl php8.3-gd

curl &lt;span class="nt"&gt;-sS&lt;/span&gt; https://getcomposer.org/installer | php
&lt;span class="nb"&gt;sudo mv &lt;/span&gt;composer.phar /usr/local/bin/composer
&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +x /usr/local/bin/composer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Node.js LTS
&lt;/h3&gt;

&lt;p&gt;Do not install Node.js from Ubuntu's default repositories. The version there is outdated. Use NodeSource:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://deb.nodesource.com/setup_22.x | &lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; bash -
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; nodejs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Python and Virtual Environment Tools
&lt;/h3&gt;

&lt;p&gt;Ubuntu 24.04 ships with Python 3.12. Add the tooling:&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;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; python3-pip python3-venv python3-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Always use virtual environments for Python applications. Never install application packages system-wide as this creates conflicts between projects.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 7: Install and Secure Your Databases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  MySQL
&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;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; mysql-server
&lt;span class="nb"&gt;sudo &lt;/span&gt;mysql_secure_installation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During secure installation answer the prompts like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validate password component: No&lt;/li&gt;
&lt;li&gt;Remove anonymous users: Yes&lt;/li&gt;
&lt;li&gt;Disallow root login remotely: Yes&lt;/li&gt;
&lt;li&gt;Remove test database: Yes&lt;/li&gt;
&lt;li&gt;Reload privilege tables: Yes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  PostgreSQL
&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;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; postgresql postgresql-contrib
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Redis
&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;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; redis-server
&lt;span class="nb"&gt;sudo sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/^bind 127.0.0.1 ::1/bind 127.0.0.1/'&lt;/span&gt; /etc/redis/redis.conf
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart redis-server
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;redis-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;sed&lt;/code&gt; command restricts Redis to localhost only. Redis with no password and public internet access is one of the most common and damaging server compromises in existence. Always keep it bound to &lt;code&gt;127.0.0.1&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accessing Databases Remotely
&lt;/h3&gt;

&lt;p&gt;Your databases are not publicly accessible and should stay that way. To connect from your local machine using a tool like TablePlus, DBeaver, or HeidiSQL, use an SSH tunnel:&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;# MySQL tunnel&lt;/span&gt;
ssh &lt;span class="nt"&gt;-L&lt;/span&gt; 3306:127.0.0.1:3306 youruser@YOUR_VPS_IP &lt;span class="nt"&gt;-N&lt;/span&gt;

&lt;span class="c"&gt;# PostgreSQL tunnel&lt;/span&gt;
ssh &lt;span class="nt"&gt;-L&lt;/span&gt; 5432:127.0.0.1:5432 youruser@YOUR_VPS_IP &lt;span class="nt"&gt;-N&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then connect your database client to &lt;code&gt;127.0.0.1&lt;/code&gt; on the forwarded port. The connection travels through your encrypted SSH session. No database port is ever exposed to the internet.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 8: Set Up Backups
&lt;/h2&gt;

&lt;p&gt;A server with no backup strategy is a server waiting to fail. Set this up before deploying anything.&lt;/p&gt;

&lt;p&gt;Create a backup script:&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 mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /opt/backups/files
&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /opt/backups/backup.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add this content:&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;#!/bin/bash&lt;/span&gt;
&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y%m%d_%H%M%S&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;BACKUP_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/opt/backups/files"&lt;/span&gt;

&lt;span class="c"&gt;# MySQL&lt;/span&gt;
mysqldump &lt;span class="nt"&gt;--all-databases&lt;/span&gt; &lt;span class="nt"&gt;--single-transaction&lt;/span&gt; | &lt;span class="nb"&gt;gzip&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BACKUP_DIR&lt;/span&gt;&lt;span class="s2"&gt;/mysql_&lt;/span&gt;&lt;span class="nv"&gt;$DATE&lt;/span&gt;&lt;span class="s2"&gt;.sql.gz"&lt;/span&gt;

&lt;span class="c"&gt;# PostgreSQL&lt;/span&gt;
&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; postgres pg_dumpall | &lt;span class="nb"&gt;gzip&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BACKUP_DIR&lt;/span&gt;&lt;span class="s2"&gt;/postgres_&lt;/span&gt;&lt;span class="nv"&gt;$DATE&lt;/span&gt;&lt;span class="s2"&gt;.sql.gz"&lt;/span&gt;

&lt;span class="c"&gt;# Nginx config&lt;/span&gt;
&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-czf&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BACKUP_DIR&lt;/span&gt;&lt;span class="s2"&gt;/nginx_&lt;/span&gt;&lt;span class="nv"&gt;$DATE&lt;/span&gt;&lt;span class="s2"&gt;.tar.gz"&lt;/span&gt; /etc/nginx/

&lt;span class="c"&gt;# Remove backups older than 7 days&lt;/span&gt;
find &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BACKUP_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-mtime&lt;/span&gt; +7 &lt;span class="nt"&gt;-delete&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make it executable and schedule it:&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 chmod&lt;/span&gt; +x /opt/backups/backup.sh
&lt;span class="nb"&gt;sudo &lt;/span&gt;crontab &lt;span class="nt"&gt;-e&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add this line to run every night at 2 AM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;0 2 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /opt/backups/backup.sh &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /var/log/backup.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Backups that exist only on the same server are not real backups. Set up offsite backup to Google Drive using rclone, to AWS S3, or to a separate server. If your VPS is compromised or the data centre has an incident, local-only backups go with it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 9: Monitor Your Server
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Native Linux Tools
&lt;/h3&gt;

&lt;p&gt;These are available immediately with no installation:&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;# Interactive process monitor&lt;/span&gt;
htop

&lt;span class="c"&gt;# Disk usage&lt;/span&gt;
&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt;

&lt;span class="c"&gt;# Memory usage&lt;/span&gt;
free &lt;span class="nt"&gt;-h&lt;/span&gt;

&lt;span class="c"&gt;# Real time Nginx access log&lt;/span&gt;
&lt;span class="nb"&gt;sudo tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/nginx/example.com.access.log

&lt;span class="c"&gt;# Real time error log&lt;/span&gt;
&lt;span class="nb"&gt;sudo tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/nginx/example.com.error.log

&lt;span class="c"&gt;# System logs&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-f&lt;/span&gt;

&lt;span class="c"&gt;# Active listening ports&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ss &lt;span class="nt"&gt;-tlnp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Netdata Dashboard
&lt;/h3&gt;

&lt;p&gt;Netdata gives you a real-time visual dashboard for CPU, RAM, disk, Nginx, MySQL, Redis, and PHP-FPM all in one place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://get.netdata.cloud/kickstart.sh &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/netdata-kickstart.sh
&lt;span class="nb"&gt;sudo &lt;/span&gt;sh /tmp/netdata-kickstart.sh &lt;span class="nt"&gt;--stable-channel&lt;/span&gt; &lt;span class="nt"&gt;--disable-telemetry&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Netdata runs on port 19999. Do not open this port publicly. Access it via SSH tunnel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-L&lt;/span&gt; 19999:localhost:19999 youruser@YOUR_VPS_IP &lt;span class="nt"&gt;-N&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then visit &lt;code&gt;http://localhost:19999&lt;/code&gt; in your browser for the full dashboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  External Uptime Monitoring
&lt;/h3&gt;

&lt;p&gt;Sign up for a free account at UptimeRobot. Add your domain and it checks every 5 minutes, sending you an email or SMS the moment your site goes down. This catches server crashes, Nginx failures, and network issues before your customers do.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Pre-Deployment Checklist
&lt;/h2&gt;

&lt;p&gt;Before pointing any domain at this server or deploying any application, run through this checklist:&lt;/p&gt;

&lt;h3&gt;
  
  
  Security
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Root SSH login is disabled&lt;/li&gt;
&lt;li&gt;[ ] Password authentication is disabled&lt;/li&gt;
&lt;li&gt;[ ] SSH key authentication is confirmed working&lt;/li&gt;
&lt;li&gt;[ ] Admin user has sudo access&lt;/li&gt;
&lt;li&gt;[ ] Firewall is active with only ports 22, 80, and 443 open&lt;/li&gt;
&lt;li&gt;[ ] No database ports are publicly accessible&lt;/li&gt;
&lt;li&gt;[ ] Redis is bound to localhost only&lt;/li&gt;
&lt;li&gt;[ ] Kernel hardening parameters are applied&lt;/li&gt;
&lt;li&gt;[ ] Unnecessary services are disabled&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Nginx is running and enabled on boot&lt;/li&gt;
&lt;li&gt;[ ] PHP-FPM is running and enabled on boot&lt;/li&gt;
&lt;li&gt;[ ] Composer is installed&lt;/li&gt;
&lt;li&gt;[ ] Node.js and npm are installed&lt;/li&gt;
&lt;li&gt;[ ] Python and venv tools are installed&lt;/li&gt;
&lt;li&gt;[ ] MySQL is installed and secured&lt;/li&gt;
&lt;li&gt;[ ] PostgreSQL is installed&lt;/li&gt;
&lt;li&gt;[ ] Redis is installed and localhost-only&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Infrastructure
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] 2GB swap file is active and permanent&lt;/li&gt;
&lt;li&gt;[ ] Hostname is set correctly&lt;/li&gt;
&lt;li&gt;[ ] Automatic security updates are enabled&lt;/li&gt;
&lt;li&gt;[ ] Backup script is created and scheduled&lt;/li&gt;
&lt;li&gt;[ ] Backup script has been tested manually at least once&lt;/li&gt;
&lt;li&gt;[ ] Offsite backup destination is configured&lt;/li&gt;
&lt;li&gt;[ ] Uptime monitoring is active&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Nginx
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Default site is disabled&lt;/li&gt;
&lt;li&gt;[ ] Server blocks exist for all your domains&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;nginx -t&lt;/code&gt; passes with no errors&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;server_tokens off&lt;/code&gt; is set&lt;/li&gt;
&lt;li&gt;[ ] Security headers are in all server blocks&lt;/li&gt;
&lt;li&gt;[ ] Log files are configured per site&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What Comes Next
&lt;/h2&gt;

&lt;p&gt;Once your checklist is complete your server is production ready. Here is what to do next:&lt;/p&gt;

&lt;p&gt;Point your DNS first. Update your domain's A record to point to your server IP at your registrar. DNS propagation takes anywhere from a few minutes to 48 hours depending on your provider.&lt;/p&gt;

&lt;p&gt;Install SSL certificates. Once DNS is pointing to your server, run Certbot to get free Let's Encrypt certificates for each domain. This takes under two minutes per domain.&lt;/p&gt;

&lt;p&gt;Deploy your applications. Each application type has its own deployment process. Treat each one as a separate focused task rather than trying to deploy everything at once.&lt;/p&gt;

&lt;p&gt;Create application databases. For each app, create a dedicated database user with only the permissions that app needs. Never use the root database user for application connections.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Mistakes to Avoid
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Skipping the firewall.&lt;/strong&gt; Every day you run without a firewall is a day your databases could be accidentally exposed. Enable UFW before installing any software.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deploying as root.&lt;/strong&gt; Applications should run as dedicated low-privilege users. Running as root means a compromised application has unlimited access to your entire server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using root for database connections.&lt;/strong&gt; Always create application-specific database users with minimal permissions. The MySQL or PostgreSQL root user should never appear in an application config file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Storing secrets in Git.&lt;/strong&gt; Your &lt;code&gt;.env&lt;/code&gt; file contains database passwords, API keys, and application secrets. It must never be committed to version control. Add &lt;code&gt;.env&lt;/code&gt; to &lt;code&gt;.gitignore&lt;/code&gt; before your first commit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skipping backups until later.&lt;/strong&gt; Later never comes. Set up backups before your first deployment. The worst time to realise you have no backups is after something breaks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opening database ports publicly.&lt;/strong&gt; Use SSH tunneling for database access from your local machine. Ports 3306 and 5432 should never appear in your UFW allowed rules.&lt;/p&gt;




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

&lt;p&gt;Setting up a VPS correctly is not complicated but it requires doing things in the right order. Here is the sequence that matters:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Inspect the server before changing anything&lt;/li&gt;
&lt;li&gt;Create a non-root admin user with SSH key authentication&lt;/li&gt;
&lt;li&gt;Test the new user, then disable root login and password auth&lt;/li&gt;
&lt;li&gt;Configure and enable the firewall before installing any software&lt;/li&gt;
&lt;li&gt;Harden the server with swap, kernel parameters, and automatic updates&lt;/li&gt;
&lt;li&gt;Install Nginx as a reverse proxy&lt;/li&gt;
&lt;li&gt;Install your application stack: PHP, Node.js, Python&lt;/li&gt;
&lt;li&gt;Install and secure your databases: MySQL, PostgreSQL, Redis&lt;/li&gt;
&lt;li&gt;Set up automated backups with offsite storage&lt;/li&gt;
&lt;li&gt;Set up monitoring and uptime alerts&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Only after completing all of these steps should you point a domain to the server and begin deploying applications.&lt;/p&gt;

&lt;p&gt;The time you invest in this setup pays back every single day your server runs without incident. A server configured this way can host multiple production applications for years without requiring significant maintenance or firefighting.&lt;/p&gt;

&lt;p&gt;For technical assistance &lt;a href="https://wa.me/254708920430?text=I'm%20interested%20in%20getting%20technical%20assistance%20from%20you" rel="noopener noreferrer"&gt;whatsapp me here&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This guide is part of a series on production server management. Other articles in the series cover Nginx as a reverse proxy in depth, setting up free SSL certificates with Let's Encrypt, and deploying applications with zero-downtime releases.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>containers</category>
      <category>linux</category>
      <category>nginx</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Let's Encrypt SSL: A Beginner's Guide to Free HTTPS for Your Website</title>
      <dc:creator>Sospeter Mong'are</dc:creator>
      <pubDate>Sun, 16 Aug 2026 19:15:27 +0000</pubDate>
      <link>https://dev.to/msnmongare/lets-encrypt-ssl-a-beginners-guide-to-free-https-for-your-website-4djh</link>
      <guid>https://dev.to/msnmongare/lets-encrypt-ssl-a-beginners-guide-to-free-https-for-your-website-4djh</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;A few years ago, adding HTTPS to your website cost money — sometimes hundreds of dollars per year for an SSL certificate. Many small websites and developers skipped it entirely, leaving their users exposed.&lt;/p&gt;

&lt;p&gt;Then in 2016, Let's Encrypt changed everything.&lt;/p&gt;

&lt;p&gt;Today, there is no excuse for a website without HTTPS. Let's Encrypt provides free, trusted, automatically renewing SSL certificates to anyone with a domain name. It powers over 300 million websites worldwide and is trusted by every major browser.&lt;/p&gt;

&lt;p&gt;This guide explains what SSL is, why it matters, how Let's Encrypt works, and exactly how to install it on an Nginx server running on Ubuntu.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is SSL and Why Does It Matter?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;SSL (Secure Sockets Layer)&lt;/strong&gt; — more accurately called TLS (Transport Layer Security) today, though the term SSL stuck — is a protocol that encrypts the connection between a visitor's browser and your web server.&lt;/p&gt;

&lt;p&gt;Without SSL, data travels across the internet in plain text. Anyone between the visitor and your server — their ISP, a coffee shop router, a malicious actor on the same network — can read that data. This includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Login credentials&lt;/li&gt;
&lt;li&gt;Credit card numbers&lt;/li&gt;
&lt;li&gt;Personal information&lt;/li&gt;
&lt;li&gt;Form submissions&lt;/li&gt;
&lt;li&gt;Session cookies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With SSL, all of that data is encrypted. Even if intercepted, it is unreadable without the decryption key.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Padlock in the Browser
&lt;/h3&gt;

&lt;p&gt;You have seen it thousands of times — the padlock icon in the browser address bar next to a URL that starts with &lt;code&gt;https://&lt;/code&gt;. That padlock means:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The connection between the browser and server is encrypted&lt;/li&gt;
&lt;li&gt;The server's identity has been verified by a trusted Certificate Authority&lt;/li&gt;
&lt;li&gt;Data cannot be intercepted or tampered with in transit&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Without SSL, modern browsers display a &lt;strong&gt;"Not Secure"&lt;/strong&gt; warning in the address bar. This alone drives visitors away and destroys trust in your website.&lt;/p&gt;

&lt;h3&gt;
  
  
  SSL Is Not Optional Anymore
&lt;/h3&gt;

&lt;p&gt;Beyond security, SSL affects your website in several concrete ways:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search Engine Rankings:&lt;/strong&gt; Google confirmed in 2014 that HTTPS is a ranking signal. Two otherwise identical websites — one with HTTPS, one without — the HTTPS site ranks higher.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Browser Warnings:&lt;/strong&gt; Chrome, Firefox, and Safari all display prominent "Not Secure" warnings on HTTP sites, especially on pages with login forms or payment fields.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTTP/2:&lt;/strong&gt; The modern HTTP/2 protocol — which makes websites significantly faster — requires HTTPS in all major browsers. No SSL means no HTTP/2.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User Trust:&lt;/strong&gt; Studies consistently show that users abandon websites that display security warnings. For any website handling user data, SSL is non-negotiable.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is a Certificate Authority?
&lt;/h2&gt;

&lt;p&gt;Your browser doesn't just trust any SSL certificate. It maintains a list of trusted &lt;strong&gt;Certificate Authorities (CAs)&lt;/strong&gt; — organisations that have been vetted and approved to issue certificates.&lt;/p&gt;

&lt;p&gt;When you install an SSL certificate, you are essentially telling browsers: &lt;em&gt;"A trusted organisation has verified that I own this domain. You can trust this connection."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Traditionally, Certificate Authorities charged for this service — anywhere from $10 to $1,000+ per year depending on the certificate type. The revenue model made sense: the CA did the work of verifying your identity and browsers trusted their signature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Let's Encrypt disrupted this entirely.&lt;/strong&gt; It is a non-profit Certificate Authority — backed by Mozilla, Google, Cisco, and others — that issues certificates for free, automatically, and without any manual verification process.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is Let's Encrypt?
&lt;/h2&gt;

&lt;p&gt;Let's Encrypt is a free, automated, open Certificate Authority launched in 2016 by the Internet Security Research Group (ISRG).&lt;/p&gt;

&lt;p&gt;Its certificates are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free&lt;/strong&gt; — no cost, ever&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trusted&lt;/strong&gt; — recognised by all major browsers and operating systems&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic&lt;/strong&gt; — issuance and renewal are fully automated&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open&lt;/strong&gt; — the process is transparent and publicly audited&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The only meaningful difference between a Let's Encrypt certificate and a paid certificate is the &lt;strong&gt;validation level&lt;/strong&gt;. Let's Encrypt issues &lt;strong&gt;Domain Validated (DV)&lt;/strong&gt; certificates, which verify you control the domain but do not verify your organisation's legal identity. For the vast majority of websites — blogs, SaaS applications, APIs, portfolios — DV certificates are perfectly sufficient.&lt;/p&gt;

&lt;p&gt;If you run a bank or a large e-commerce platform that wants to display your company name in the browser bar, you might want an &lt;strong&gt;Extended Validation (EV)&lt;/strong&gt; certificate from a paid CA. For everything else, Let's Encrypt is the right choice.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Let's Encrypt Works
&lt;/h2&gt;

&lt;p&gt;Let's Encrypt uses a protocol called &lt;strong&gt;ACME (Automatic Certificate Management Environment)&lt;/strong&gt; to automate the entire certificate lifecycle.&lt;/p&gt;

&lt;p&gt;Here is the process, simplified:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1 — Domain Ownership Verification
&lt;/h3&gt;

&lt;p&gt;Before issuing a certificate for &lt;code&gt;example.com&lt;/code&gt;, Let's Encrypt needs to verify you actually control that domain. It does this through a &lt;strong&gt;challenge&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The most common challenge type is the &lt;strong&gt;HTTP-01 challenge&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Let's Encrypt tells your server to place a specific file at a specific URL — for example &lt;code&gt;http://example.com/.well-known/acme-challenge/randomtoken&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Let's Encrypt then fetches that URL from its servers&lt;/li&gt;
&lt;li&gt;If the file is there with the correct content, domain ownership is proven&lt;/li&gt;
&lt;li&gt;The certificate is issued&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This entire process happens in seconds and is fully automated by Certbot.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 — Certificate Issuance
&lt;/h3&gt;

&lt;p&gt;Once domain ownership is verified, Let's Encrypt issues a certificate valid for &lt;strong&gt;90 days&lt;/strong&gt;. This short validity period is intentional — it limits the window of exposure if a certificate is ever compromised, and it forces automation of renewal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3 — Automatic Renewal
&lt;/h3&gt;

&lt;p&gt;Certbot installs a systemd timer (or cron job) that runs twice daily. It checks whether any certificate is within 30 days of expiring. If so, it automatically renews it — re-running the challenge, obtaining a new certificate, and reloading Nginx — all without any manual intervention.&lt;/p&gt;

&lt;p&gt;In practice, your certificates renew themselves every 60-70 days, long before the 90-day expiry.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is Certbot?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Certbot&lt;/strong&gt; is the official client tool for Let's Encrypt, maintained by the Electronic Frontier Foundation (EFF). It is the software you install on your server that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Communicates with Let's Encrypt's ACME servers&lt;/li&gt;
&lt;li&gt;Handles the domain ownership challenge automatically&lt;/li&gt;
&lt;li&gt;Obtains and installs the SSL certificate&lt;/li&gt;
&lt;li&gt;Configures Nginx (or Apache) to use it&lt;/li&gt;
&lt;li&gt;Sets up automatic renewal&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without Certbot, you would need to manually interact with Let's Encrypt's API. Certbot makes the entire process a single command.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisites Before Running Certbot
&lt;/h2&gt;

&lt;p&gt;Before you can install an SSL certificate, three things must be true:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Your Domain Must Point to Your Server
&lt;/h3&gt;

&lt;p&gt;Let's Encrypt verifies domain ownership by making an HTTP request to your domain. If your domain's DNS is not pointing to your server's IP address, the challenge will fail and no certificate will be issued.&lt;/p&gt;

&lt;p&gt;Check your DNS records at your registrar. You need:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Record&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;example.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;Your server IP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;www.example.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;Your server IP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;api.example.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;Your server IP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;DNS changes can take anywhere from a few minutes to 48 hours to propagate, depending on your registrar and TTL settings. You can verify DNS has propagated using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig example.com +short
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use an online tool like &lt;a href="https://dnschecker.org" rel="noopener noreferrer"&gt;dnschecker.org&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Nginx Must Be Running and Configured
&lt;/h3&gt;

&lt;p&gt;Certbot needs Nginx to be running with a server block configured for your domain. The server block does not need SSL yet — Certbot adds that. It just needs to exist and be active.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Port 80 Must Be Open
&lt;/h3&gt;

&lt;p&gt;Certbot uses port 80 (HTTP) to complete the domain challenge. Your firewall must allow incoming traffic on port 80. If you followed the server setup guide in this series, UFW already has port 80 open.&lt;/p&gt;




&lt;h2&gt;
  
  
  Installing Certbot on Ubuntu with Nginx
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1 — Install Certbot
&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;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; certbot python3-certbot-nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;python3-certbot-nginx&lt;/code&gt; plugin allows Certbot to automatically modify your Nginx configuration to enable SSL — you do not need to edit any config files manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 — Obtain Your First Certificate
&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;certbot &lt;span class="nt"&gt;--nginx&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; example.com &lt;span class="nt"&gt;-d&lt;/span&gt; www.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What the flags mean:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--nginx&lt;/code&gt; — use the Nginx plugin to automatically configure SSL&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-d example.com&lt;/code&gt; — issue certificate for this domain&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-d www.example.com&lt;/code&gt; — include this as an additional domain on the same certificate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can include multiple &lt;code&gt;-d&lt;/code&gt; flags to cover multiple subdomains on one certificate:&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;certbot &lt;span class="nt"&gt;--nginx&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; example.com &lt;span class="nt"&gt;-d&lt;/span&gt; www.example.com &lt;span class="nt"&gt;-d&lt;/span&gt; api.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3 — Answer the Prompts
&lt;/h3&gt;

&lt;p&gt;Certbot will ask you a few questions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Enter email address: you@example.com
Agree to terms of service: Y
Share email with EFF: N (your choice)
Redirect HTTP to HTTPS: 2 (Always redirect — recommended)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Choose option &lt;code&gt;2&lt;/code&gt; for the redirect. This automatically adds an Nginx rule that sends all HTTP traffic to HTTPS, so visitors who type &lt;code&gt;http://example.com&lt;/code&gt; are seamlessly redirected to &lt;code&gt;https://example.com&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 — What Certbot Does Automatically
&lt;/h3&gt;

&lt;p&gt;After you answer the prompts, Certbot:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Contacts Let's Encrypt servers&lt;/li&gt;
&lt;li&gt;Places a challenge file in your web root&lt;/li&gt;
&lt;li&gt;Let's Encrypt verifies the file&lt;/li&gt;
&lt;li&gt;Certificate is issued and saved to &lt;code&gt;/etc/letsencrypt/live/example.com/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Certbot modifies your Nginx config to enable SSL on port 443&lt;/li&gt;
&lt;li&gt;Certbot adds the HTTP to HTTPS redirect on port 80&lt;/li&gt;
&lt;li&gt;Nginx is reloaded&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your site is now serving HTTPS. The entire process takes under 60 seconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Your Nginx Config Looks Like After Certbot
&lt;/h2&gt;

&lt;p&gt;Before Certbot, your Nginx server block looked like this:&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;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;80&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;example.com&lt;/span&gt; &lt;span class="s"&gt;www.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="n"&gt;/var/www/example.com/public&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;...&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After Certbot runs, it transforms it into:&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;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;80&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;example.com&lt;/span&gt; &lt;span class="s"&gt;www.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt; &lt;span class="s"&gt;https://&lt;/span&gt;&lt;span class="nv"&gt;$host$request_uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&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;example.com&lt;/span&gt; &lt;span class="s"&gt;www.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="n"&gt;/var/www/example.com/public&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;/etc/letsencrypt/live/example.com/fullchain.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;/etc/letsencrypt/live/example.com/privkey.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="n"&gt;/etc/letsencrypt/options-ssl-nginx.conf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_dhparam&lt;/span&gt; &lt;span class="n"&gt;/etc/letsencrypt/ssl-dhparams.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;...your&lt;/span&gt; &lt;span class="s"&gt;existing&lt;/span&gt; &lt;span class="s"&gt;config...&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first server block catches all HTTP traffic and redirects it to HTTPS. The second handles all HTTPS traffic with your SSL certificate.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where Certbot Stores Your Certificates
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/etc/letsencrypt/
├── live/
│   └── example.com/
│       ├── fullchain.pem    ← Your certificate + intermediate chain
│       ├── privkey.pem      ← Your private key (keep this secret)
│       ├── cert.pem         ← Your certificate only
│       └── chain.pem        ← Intermediate certificates only
├── archive/
│   └── example.com/         ← Historical certificates (all versions)
└── renewal/
    └── example.com.conf     ← Renewal configuration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; The files in &lt;code&gt;live/&lt;/code&gt; are actually symbolic links pointing to the latest version in &lt;code&gt;archive/&lt;/code&gt;. When Certbot renews your certificate, it creates new files in &lt;code&gt;archive/&lt;/code&gt; and updates the symlinks — so your Nginx config never needs to change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Never delete anything in &lt;code&gt;/etc/letsencrypt/&lt;/code&gt;&lt;/strong&gt; unless you know exactly what you are doing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Automatic Certificate Renewal
&lt;/h2&gt;

&lt;p&gt;This is where Let's Encrypt's real power shows. Certbot installs a systemd timer that runs twice daily:&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;# Check the timer status&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status certbot.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will see it is active and scheduled to run at random times twice per day. The randomisation prevents all Certbot installations worldwide from hitting Let's Encrypt's servers simultaneously.&lt;/p&gt;

&lt;p&gt;When the timer runs, Certbot checks each certificate. If any certificate expires within 30 days, it renews it automatically. In practice your certificates renew around the 60-day mark — well before the 90-day expiry.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test Renewal Without Actually Renewing
&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;certbot renew &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This simulates the entire renewal process without actually changing anything. Run this after installation to confirm renewal will work when the time comes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expected output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Congratulations, all simulated renewals succeeded:
  /etc/letsencrypt/live/example.com/fullchain.pem (success)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Managing Multiple Domains
&lt;/h2&gt;

&lt;p&gt;If you host multiple websites on one server, you can have separate certificates for each domain:&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;# Certificate for your main app&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;certbot &lt;span class="nt"&gt;--nginx&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; example.com &lt;span class="nt"&gt;-d&lt;/span&gt; www.example.com

&lt;span class="c"&gt;# Certificate for your API&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;certbot &lt;span class="nt"&gt;--nginx&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; api.example.com

&lt;span class="c"&gt;# Certificate for another site&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;certbot &lt;span class="nt"&gt;--nginx&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; anotherdomain.com &lt;span class="nt"&gt;-d&lt;/span&gt; www.anotherdomain.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each domain gets its own certificate stored separately in &lt;code&gt;/etc/letsencrypt/live/&lt;/code&gt;. Certbot manages renewal for all of them automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;List all your certificates:&lt;/strong&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="nb"&gt;sudo &lt;/span&gt;certbot certificates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Found the following certs:
  Certificate Name: example.com
    Domains: example.com www.example.com
    Expiry Date: 2026-11-14 (VALID: 89 days)
    Certificate Path: /etc/letsencrypt/live/example.com/fullchain.pem

  Certificate Name: api.example.com
    Domains: api.example.com
    Expiry Date: 2026-11-14 (VALID: 89 days)
    Certificate Path: /etc/letsencrypt/live/api.example.com/fullchain.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Testing Your SSL Installation
&lt;/h2&gt;

&lt;p&gt;After installing your certificate, verify it is working correctly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test in Your Browser
&lt;/h3&gt;

&lt;p&gt;Visit &lt;code&gt;https://example.com&lt;/code&gt; and click the padlock icon. You should see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connection is secure&lt;/li&gt;
&lt;li&gt;Certificate is valid&lt;/li&gt;
&lt;li&gt;Issued by: Let's Encrypt&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Test with SSL Labs
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.ssllabs.com/ssltest/" rel="noopener noreferrer"&gt;SSL Labs&lt;/a&gt; provides a free, detailed SSL analysis of your domain. It checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Certificate validity&lt;/li&gt;
&lt;li&gt;Certificate chain&lt;/li&gt;
&lt;li&gt;Protocol support (TLS 1.2, TLS 1.3)&lt;/li&gt;
&lt;li&gt;Cipher strength&lt;/li&gt;
&lt;li&gt;Known vulnerabilities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A properly configured Let's Encrypt certificate with default Certbot settings typically scores &lt;strong&gt;A&lt;/strong&gt; on SSL Labs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test from the Command Line
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-I&lt;/span&gt; https://example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Expected output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;
&lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
&lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;HTTP/2 200&lt;/code&gt; confirms both HTTPS and HTTP/2 are working.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Problems and Solutions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Challenge Failed — Domain Not Pointing to Server
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Error:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Challenge failed for domain example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; DNS is not pointing to your server yet, or DNS has not propagated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&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="c"&gt;# Check what IP your domain resolves to&lt;/span&gt;
dig example.com +short

&lt;span class="c"&gt;# It must match your server IP&lt;/span&gt;
curl ifconfig.me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait for DNS propagation and try again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Port 80 Is Blocked
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Error:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Connection refused on port 80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Your firewall is blocking HTTP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&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="nb"&gt;sudo &lt;/span&gt;ufw allow http
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Nginx Configuration Error
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Error:&lt;/strong&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;nginx:&lt;/span&gt; &lt;span class="s"&gt;configuration&lt;/span&gt; &lt;span class="s"&gt;file&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt; &lt;span class="s"&gt;failed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&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="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fix whatever error it reports before running Certbot again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Certificate Already Exists
&lt;/h3&gt;

&lt;p&gt;If you run Certbot for a domain that already has a certificate, it will ask whether to renew, expand, or reinstall. Choose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Renew&lt;/strong&gt; — get a fresh certificate for the same domains&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expand&lt;/strong&gt; — add new domains to the existing certificate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reinstall&lt;/strong&gt; — reinstall the existing certificate without changes&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Rate Limits
&lt;/h2&gt;

&lt;p&gt;Let's Encrypt has rate limits to prevent abuse:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Limit&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Certificates per domain per week&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duplicate certificates per week&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failed validations per hour&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In practice you will never hit these limits in normal use. They only matter if you are repeatedly requesting certificates for the same domain due to testing or errors.&lt;/p&gt;

&lt;p&gt;If you do hit a rate limit, you must wait before trying again. To avoid this during testing, use Let's Encrypt's &lt;strong&gt;staging environment&lt;/strong&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="nb"&gt;sudo &lt;/span&gt;certbot &lt;span class="nt"&gt;--nginx&lt;/span&gt; &lt;span class="nt"&gt;--staging&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Staging certificates are not trusted by browsers but have much higher rate limits. Use staging to test your setup, then run without &lt;code&gt;--staging&lt;/code&gt; for the real certificate.&lt;/p&gt;




&lt;h2&gt;
  
  
  Revoking a Certificate
&lt;/h2&gt;

&lt;p&gt;If your private key is ever compromised, revoke the certificate immediately:&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;certbot revoke &lt;span class="nt"&gt;--cert-path&lt;/span&gt; /etc/letsencrypt/live/example.com/cert.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then obtain a new certificate:&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;certbot &lt;span class="nt"&gt;--nginx&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Bigger Picture — HTTPS Everywhere
&lt;/h2&gt;

&lt;p&gt;Let's Encrypt's mission is to make HTTPS universal. Before it existed, the friction and cost of SSL certificates meant that millions of websites transmitted user data in plain text. Today there is no technical or financial barrier to HTTPS.&lt;/p&gt;

&lt;p&gt;If you are building a web application, an API, or any internet-facing service, HTTPS is not a feature — it is a baseline requirement. Let's Encrypt removes every excuse not to have it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Reference — Certbot Commands
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install Certbot&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; certbot python3-certbot-nginx

&lt;span class="c"&gt;# Obtain certificate for a domain&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;certbot &lt;span class="nt"&gt;--nginx&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; example.com &lt;span class="nt"&gt;-d&lt;/span&gt; www.example.com

&lt;span class="c"&gt;# List all certificates&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;certbot certificates

&lt;span class="c"&gt;# Test renewal (dry run)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;certbot renew &lt;span class="nt"&gt;--dry-run&lt;/span&gt;

&lt;span class="c"&gt;# Force renewal immediately&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;certbot renew &lt;span class="nt"&gt;--force-renewal&lt;/span&gt;

&lt;span class="c"&gt;# Revoke a certificate&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;certbot revoke &lt;span class="nt"&gt;--cert-path&lt;/span&gt; /etc/letsencrypt/live/example.com/cert.pem

&lt;span class="c"&gt;# Delete a certificate&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;certbot delete &lt;span class="nt"&gt;--cert-name&lt;/span&gt; example.com

&lt;span class="c"&gt;# Check renewal timer&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status certbot.timer

&lt;span class="c"&gt;# Test staging (no rate limits)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;certbot &lt;span class="nt"&gt;--nginx&lt;/span&gt; &lt;span class="nt"&gt;--staging&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;p&gt;Here is everything covered in this guide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SSL encrypts the connection between your visitor and server — it is mandatory for any modern website&lt;/li&gt;
&lt;li&gt;Let's Encrypt is a free, trusted, automated Certificate Authority that issues 90-day certificates&lt;/li&gt;
&lt;li&gt;Certbot is the tool that talks to Let's Encrypt, installs your certificate, configures Nginx, and renews automatically&lt;/li&gt;
&lt;li&gt;Domain ownership is verified via the HTTP-01 challenge — Let's Encrypt checks a file on your server&lt;/li&gt;
&lt;li&gt;Certificates auto-renew every 60 days via a systemd timer — zero manual work required&lt;/li&gt;
&lt;li&gt;Always run &lt;code&gt;certbot renew --dry-run&lt;/code&gt; after setup to confirm renewal works&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;--staging&lt;/code&gt; for testing to avoid rate limits&lt;/li&gt;
&lt;li&gt;SSL Labs gives you an independent grade of your SSL configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When your domain is pointed to your server and Nginx is configured, running Let's Encrypt takes less than two minutes. There is genuinely no reason to run any website without it.&lt;/p&gt;

</description>
      <category>security</category>
      <category>productivity</category>
      <category>linux</category>
    </item>
    <item>
      <title>What Is Nginx? A Beginner's Guide to the Web Server Powering the Modern Internet</title>
      <dc:creator>Sospeter Mong'are</dc:creator>
      <pubDate>Sun, 16 Aug 2026 16:54:22 +0000</pubDate>
      <link>https://dev.to/msnmongare/what-is-nginx-a-beginners-guide-to-the-web-server-powering-the-modern-internet-2b8i</link>
      <guid>https://dev.to/msnmongare/what-is-nginx-a-beginners-guide-to-the-web-server-powering-the-modern-internet-2b8i</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;If you have ever set up a website, deployed a web application, or rented a VPS (Virtual Private Server), you have almost certainly encountered the name &lt;strong&gt;Nginx&lt;/strong&gt; (pronounced &lt;em&gt;engine-x&lt;/em&gt;). It powers some of the busiest websites on the internet — including Netflix, Dropbox, and WordPress.com — yet many beginners find it intimidating at first glance.&lt;/p&gt;

&lt;p&gt;This guide will change that.&lt;/p&gt;

&lt;p&gt;By the end of this article, you will understand exactly what Nginx is, why it exists, how it works, and how to use it to serve real websites on a Linux server. No prior experience with web servers is required.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is Nginx?
&lt;/h2&gt;

&lt;p&gt;Nginx is a &lt;strong&gt;web server&lt;/strong&gt; — software that listens for requests coming from the internet and responds with web pages, files, or data.&lt;/p&gt;

&lt;p&gt;But Nginx is more than just a web server. It is also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;reverse proxy&lt;/strong&gt; — it sits in front of your applications and forwards requests to them&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;load balancer&lt;/strong&gt; — it can distribute traffic across multiple servers&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;HTTP cache&lt;/strong&gt; — it can store and serve cached responses to reduce server load&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;SSL terminator&lt;/strong&gt; — it handles HTTPS encryption so your applications don't have to&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of Nginx as the &lt;strong&gt;reception desk&lt;/strong&gt; of a large office building. Every visitor (web request) walks in through the front door (port 80 or 443). The receptionist (Nginx) greets them, figures out where they need to go, and directs them to the right department — the Laravel team, the Node.js team, or the Python team. The visitor never wanders the building themselves.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Not Just Use Apache?
&lt;/h2&gt;

&lt;p&gt;Apache is the other major web server and has been around since 1995. For years it dominated the web. So why does Nginx exist?&lt;/p&gt;

&lt;p&gt;The answer comes down to &lt;strong&gt;architecture&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Apache creates a new thread or process for every incoming connection. This works fine for low traffic, but under heavy load — thousands of simultaneous connections — Apache consumes enormous amounts of RAM and CPU.&lt;/p&gt;

&lt;p&gt;Nginx was built in 2004 specifically to solve this problem. It uses an &lt;strong&gt;event-driven, asynchronous architecture&lt;/strong&gt;. Instead of one thread per connection, a single Nginx worker process can handle thousands of simultaneous connections efficiently using very little memory.&lt;/p&gt;

&lt;p&gt;The result:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Apache&lt;/th&gt;
&lt;th&gt;Nginx&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Architecture&lt;/td&gt;
&lt;td&gt;Thread per connection&lt;/td&gt;
&lt;td&gt;Event-driven&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory usage under load&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Static file serving&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reverse proxy&lt;/td&gt;
&lt;td&gt;Possible&lt;/td&gt;
&lt;td&gt;Native, highly optimised&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Configuration style&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.htaccess&lt;/code&gt; per directory&lt;/td&gt;
&lt;td&gt;Centralised config files&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For modern application servers running Laravel, Node.js, or Python, Nginx is the industry standard choice.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Nginx Fits Into a Real Server
&lt;/h2&gt;

&lt;p&gt;Here is the architecture that professional developers use on production servers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Internet
    |
    ▼
[ Nginx — Port 80/443 ]
    |
    ├──► example.com        → PHP-FPM (Laravel app)
    ├──► api.example.com    → Node.js running on port 3000
    └──► app.example.com    → Python running on port 8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that &lt;strong&gt;none of the applications talk directly to the internet&lt;/strong&gt;. Only Nginx does. This is the reverse proxy pattern and it is fundamental to how modern servers work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is this pattern so powerful?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Security:&lt;/strong&gt; Your Node.js app running on port 3000 is never exposed to the internet. UFW (the firewall) blocks port 3000 publicly. Only Nginx — on port 443 — is reachable, and Nginx decides what to forward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SSL in one place:&lt;/strong&gt; Instead of configuring HTTPS in your Laravel app, your Node.js app, and your Python app separately, you configure it once in Nginx. All apps automatically get HTTPS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multiple apps on one server:&lt;/strong&gt; Without a reverse proxy, you could only run one application per server (one thing can listen on port 80 at a time). With Nginx routing by domain name, you can run dozens of applications on a single server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Static files:&lt;/strong&gt; Nginx serves static files (images, CSS, JavaScript) directly from disk at incredible speed — without involving PHP, Node.js, or Python at all. This dramatically reduces load on your application.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understanding Nginx Configuration
&lt;/h2&gt;

&lt;p&gt;Nginx configuration lives in &lt;code&gt;/etc/nginx/&lt;/code&gt; on Ubuntu/Debian systems. Here is the directory layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/etc/nginx/
├── nginx.conf              ← Main configuration file
├── sites-available/        ← All site configs (active or not)
│   ├── example.com
│   └── api.example.com
├── sites-enabled/          ← Symlinks to active sites only
│   ├── example.com → ../sites-available/example.com
│   └── api.example.com → ../sites-available/api.example.com
├── conf.d/                 ← Additional configuration fragments
└── snippets/               ← Reusable configuration pieces
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The sites-available and sites-enabled pattern
&lt;/h3&gt;

&lt;p&gt;This is an elegant system. You write your site configuration in &lt;code&gt;sites-available/&lt;/code&gt;. To activate it, you create a symbolic link (a shortcut) in &lt;code&gt;sites-enabled/&lt;/code&gt;. To deactivate a site without deleting its configuration, you simply remove the symlink.&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;# Enable a site&lt;/span&gt;
&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/

&lt;span class="c"&gt;# Disable a site (config is preserved)&lt;/span&gt;
&lt;span class="nb"&gt;sudo rm&lt;/span&gt; /etc/nginx/sites-enabled/example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Server Blocks — Nginx's Virtual Hosts
&lt;/h2&gt;

&lt;p&gt;In Nginx, each website or application is configured using a &lt;strong&gt;server block&lt;/strong&gt;. This is equivalent to Apache's Virtual Hosts. A server block tells Nginx: &lt;em&gt;"When a request comes in for this domain name, here is how to handle it."&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 1: Serving a Static HTML Website
&lt;/h3&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;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="s"&gt;[::]:80&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;example.com&lt;/span&gt; &lt;span class="s"&gt;www.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="n"&gt;/var/www/example.com/public&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;index&lt;/span&gt; &lt;span class="s"&gt;index.html&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;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="n"&gt;/var/log/nginx/example.com.access.log&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;error_log&lt;/span&gt;  &lt;span class="n"&gt;/var/log/nginx/example.com.error.log&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;p&gt;Let's break this down line by line:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;listen 80&lt;/code&gt; — listen for HTTP traffic on port 80&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;listen [::]:80&lt;/code&gt; — also listen on IPv6&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;server_name example.com www.example.com&lt;/code&gt; — this block handles requests for these domain names&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;root /var/www/example.com/public&lt;/code&gt; — files are served from this directory&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;index index.html&lt;/code&gt; — the default file to serve when a directory is requested&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;try_files $uri $uri/ =404&lt;/code&gt; — try to find the requested file; return 404 if not found&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;access_log&lt;/code&gt; and &lt;code&gt;error_log&lt;/code&gt; — where to write logs for this site&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Example 2: Serving a Laravel (PHP) Application
&lt;/h3&gt;

&lt;p&gt;Laravel is a PHP framework that requires PHP-FPM to process &lt;code&gt;.php&lt;/code&gt; files. Here is how Nginx is configured to work with it:&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;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;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="s"&gt;[::]:80&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;example.com&lt;/span&gt; &lt;span class="s"&gt;www.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="n"&gt;/var/www/example.com/public&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;index&lt;/span&gt; &lt;span class="s"&gt;index.php&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;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="n"&gt;/index.php?&lt;/span&gt;&lt;span class="nv"&gt;$query_string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt; &lt;span class="sr"&gt;\.php$&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_pass&lt;/span&gt; &lt;span class="s"&gt;unix:/var/run/php/php8.3-fpm.sock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_param&lt;/span&gt; &lt;span class="s"&gt;SCRIPT_FILENAME&lt;/span&gt; &lt;span class="nv"&gt;$realpath_root$fastcgi_script_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="s"&gt;fastcgi_params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt; &lt;span class="sr"&gt;/\.(?!well-known).*&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;deny&lt;/span&gt; &lt;span class="s"&gt;all&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="n"&gt;/var/log/nginx/example.com.access.log&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;error_log&lt;/span&gt;  &lt;span class="n"&gt;/var/log/nginx/example.com.error.log&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;p&gt;The key addition here is the &lt;code&gt;location ~ \.php$&lt;/code&gt; block. This tells Nginx: &lt;em&gt;"For any request ending in &lt;code&gt;.php&lt;/code&gt;, don't serve it as a static file — pass it to PHP-FPM for processing."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;PHP-FPM (FastCGI Process Manager) is a separate service that runs PHP code and returns the result to Nginx. They communicate via a &lt;strong&gt;Unix socket&lt;/strong&gt; (&lt;code&gt;php8.3-fpm.sock&lt;/code&gt;) — a fast, secure local communication channel.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;location ~ /\.(?!well-known).*&lt;/code&gt; block denies access to hidden files (files starting with a dot, like &lt;code&gt;.env&lt;/code&gt;). This is a critical security rule — your &lt;code&gt;.env&lt;/code&gt; file contains database passwords and application secrets, and it must never be publicly accessible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 3: Reverse Proxy to Node.js
&lt;/h3&gt;

&lt;p&gt;When your Node.js/Express application is running on port 3000, Nginx forwards requests to it like this:&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;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;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="s"&gt;[::]:80&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;api.example.com&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://localhost:3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_http_version&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Upgrade&lt;/span&gt; &lt;span class="nv"&gt;$http_upgrade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Connection&lt;/span&gt; &lt;span class="s"&gt;'upgrade'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-For&lt;/span&gt; &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span class="nv"&gt;$scheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="n"&gt;/var/log/nginx/api.example.com.access.log&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;error_log&lt;/span&gt;  &lt;span class="n"&gt;/var/log/nginx/api.example.com.error.log&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;p&gt;The &lt;code&gt;proxy_pass http://localhost:3000&lt;/code&gt; directive is the core of reverse proxying. Every request that hits &lt;code&gt;api.example.com&lt;/code&gt; is forwarded to your Node.js process running locally on port 3000.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;proxy_set_header&lt;/code&gt; directives pass important information to your Node.js app — particularly &lt;code&gt;X-Real-IP&lt;/code&gt; and &lt;code&gt;X-Forwarded-For&lt;/code&gt;, which tell your application the real IP address of the visitor (since from Node.js's perspective, all requests appear to come from localhost).&lt;/p&gt;




&lt;h2&gt;
  
  
  Important Security Headers
&lt;/h2&gt;

&lt;p&gt;Every Nginx server block should include these security headers:&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;add_header&lt;/span&gt; &lt;span class="s"&gt;X-Frame-Options&lt;/span&gt; &lt;span class="s"&gt;"SAMEORIGIN"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;X-Content-Type-Options&lt;/span&gt; &lt;span class="s"&gt;"nosniff"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;X-XSS-Protection&lt;/span&gt; &lt;span class="s"&gt;"1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;mode=block"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;X-Frame-Options SAMEORIGIN&lt;/strong&gt; — prevents your site from being embedded in an iframe on another website (protects against clickjacking attacks)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;X-Content-Type-Options nosniff&lt;/strong&gt; — prevents browsers from guessing the content type of a response (protects against MIME type confusion attacks)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;X-XSS-Protection&lt;/strong&gt; — enables the browser's built-in cross-site scripting filter&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Hiding the Nginx Version Number
&lt;/h2&gt;

&lt;p&gt;By default, Nginx tells the world exactly which version it is running. This is visible in HTTP response headers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Server: nginx/1.24.0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a gift to attackers — they can look up known vulnerabilities for that exact version. One line in your &lt;code&gt;nginx.conf&lt;/code&gt; fixes this:&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;server_tokens&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this, the header simply shows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Server: nginx
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Testing and Reloading Configuration
&lt;/h2&gt;

&lt;p&gt;This is the most important operational habit with Nginx. &lt;strong&gt;Always test your configuration before reloading.&lt;/strong&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="c"&gt;# Test configuration syntax&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt;

&lt;span class="c"&gt;# If the test passes, reload gracefully&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reload nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference between &lt;code&gt;reload&lt;/code&gt; and &lt;code&gt;restart&lt;/code&gt; is important:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;reload&lt;/code&gt; — applies the new configuration without dropping existing connections. Zero downtime.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;restart&lt;/code&gt; — stops and starts Nginx completely. Active connections are dropped.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Always use &lt;code&gt;reload&lt;/code&gt; in production. Only use &lt;code&gt;restart&lt;/code&gt; if Nginx is genuinely broken and needs a full restart.&lt;/p&gt;

&lt;p&gt;A passing test looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you see any errors, fix them before reloading. A broken configuration with a running &lt;code&gt;reload&lt;/code&gt; command will leave the old configuration running — Nginx is smart enough not to apply a broken config.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understanding Nginx Worker Processes
&lt;/h2&gt;

&lt;p&gt;When Nginx starts on a modern server, you will see multiple processes running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;nginx: master process
nginx: worker process
nginx: worker process
nginx: worker process
nginx: worker process
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;master process&lt;/strong&gt; manages the workers and handles configuration reloads. It runs as root so it can bind to ports 80 and 443.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;worker processes&lt;/strong&gt; handle the actual connections. They run as the &lt;code&gt;www-data&lt;/code&gt; user — a low-privilege account — so even if a worker is compromised, the damage is limited.&lt;/p&gt;

&lt;p&gt;By default on Ubuntu 24.04, Nginx automatically sets the number of workers to match your CPU core count. On a 4-core server you get 4 workers. This is the correct production setting.&lt;/p&gt;




&lt;h2&gt;
  
  
  Nginx Logs
&lt;/h2&gt;

&lt;p&gt;Every site configured in Nginx writes to its own log files:&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;# Access log — every request&lt;/span&gt;
/var/log/nginx/example.com.access.log

&lt;span class="c"&gt;# Error log — problems and warnings&lt;/span&gt;
/var/log/nginx/example.com.error.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To watch requests in real time:&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 tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/nginx/example.com.access.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To watch errors:&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 tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/nginx/example.com.error.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The access log shows you every request: the IP address, timestamp, requested URL, HTTP status code, and response size. The error log shows you configuration problems, upstream connection failures, and PHP-FPM errors.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Nginx Commands Reference
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check if Nginx is running&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status nginx

&lt;span class="c"&gt;# Start Nginx&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start nginx

&lt;span class="c"&gt;# Stop Nginx&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl stop nginx

&lt;span class="c"&gt;# Reload configuration (zero downtime)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reload nginx

&lt;span class="c"&gt;# Test configuration syntax&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt;

&lt;span class="c"&gt;# Enable a site&lt;/span&gt;
&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/

&lt;span class="c"&gt;# Disable a site&lt;/span&gt;
&lt;span class="nb"&gt;sudo rm&lt;/span&gt; /etc/nginx/sites-enabled/example.com

&lt;span class="c"&gt;# View access logs in real time&lt;/span&gt;
&lt;span class="nb"&gt;sudo tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/nginx/example.com.access.log

&lt;span class="c"&gt;# View error logs in real time&lt;/span&gt;
&lt;span class="nb"&gt;sudo tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/nginx/example.com.error.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What Comes After Nginx?
&lt;/h2&gt;

&lt;p&gt;Once Nginx is installed and configured, the typical next steps in building a production server are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;PHP and PHP-FPM&lt;/strong&gt; — to run Laravel and other PHP applications&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js&lt;/strong&gt; — to run Express.js APIs and applications&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSL certificates with Let's Encrypt&lt;/strong&gt; — to serve everything over HTTPS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MySQL or PostgreSQL&lt;/strong&gt; — for your application databases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis&lt;/strong&gt; — for caching and queues&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All of these work behind Nginx. The internet sees only Nginx. Everything else is internal.&lt;/p&gt;




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

&lt;p&gt;Nginx is the backbone of modern web server infrastructure. Here is what you have learned in this guide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nginx is a web server, reverse proxy, load balancer, and SSL terminator&lt;/li&gt;
&lt;li&gt;It uses an event-driven architecture that handles thousands of connections efficiently&lt;/li&gt;
&lt;li&gt;The reverse proxy pattern keeps your applications hidden from the internet&lt;/li&gt;
&lt;li&gt;Server blocks define how Nginx handles requests for each domain&lt;/li&gt;
&lt;li&gt;PHP applications use PHP-FPM, Node.js and Python apps use &lt;code&gt;proxy_pass&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Always test configuration with &lt;code&gt;nginx -t&lt;/code&gt; before reloading&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;reload&lt;/code&gt; not &lt;code&gt;restart&lt;/code&gt; in production to avoid downtime&lt;/li&gt;
&lt;li&gt;Security headers and &lt;code&gt;server_tokens off&lt;/code&gt; are essential hardening steps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nginx rewards the time you invest in learning it. Once you understand its architecture, managing multiple applications on a single server becomes straightforward, predictable, and reliable.&lt;/p&gt;

</description>
      <category>nginx</category>
      <category>apache</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Airflow vs Cron: Is Apache Airflow Just a Cron Job?</title>
      <dc:creator>Sospeter Mong'are</dc:creator>
      <pubDate>Wed, 12 Aug 2026 11:31:46 +0000</pubDate>
      <link>https://dev.to/msnmongare/airflow-vs-cron-is-apache-airflow-just-a-cron-job-36hd</link>
      <guid>https://dev.to/msnmongare/airflow-vs-cron-is-apache-airflow-just-a-cron-job-36hd</guid>
      <description>&lt;p&gt;If you're new to data engineering, you may come across &lt;strong&gt;Apache Airflow&lt;/strong&gt; and wonder:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Isn't Airflow basically just a fancy cron job?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The short answer is &lt;strong&gt;not exactly&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Airflow can do what cron does - schedule tasks to run at specific times - but Airflow is designed to solve a much bigger problem.&lt;/p&gt;

&lt;p&gt;A useful way to think about it is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Cron is primarily a scheduler. Airflow is a workflow orchestrator that also provides scheduling.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's break that down.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Cron?
&lt;/h2&gt;

&lt;p&gt;Cron is a time-based job scheduler available on Unix and Linux systems.&lt;/p&gt;

&lt;p&gt;You can tell cron:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Run this command every day at 2 AM."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;0 2 &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; python process_data.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Every day at 2:00 AM
        |
        v
Run process_data.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the main job of cron.&lt;/p&gt;

&lt;p&gt;It answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;When should this command run?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Cron is excellent for simple scheduled tasks.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Backing up a database&lt;/li&gt;
&lt;li&gt;Cleaning temporary files&lt;/li&gt;
&lt;li&gt;Running a simple script&lt;/li&gt;
&lt;li&gt;Sending a scheduled report&lt;/li&gt;
&lt;li&gt;Running a maintenance task&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But things become more complicated when your process has many steps and dependencies.&lt;/p&gt;




&lt;h1&gt;
  
  
  What is Airflow?
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Apache Airflow&lt;/strong&gt; is a platform for developing, scheduling, and monitoring workflows.&lt;/p&gt;

&lt;p&gt;Instead of simply saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Run this command at 2 AM."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;you can define an entire workflow.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2:00 AM
   |
   v
Extract data
   |
   v
Load staging tables
   |
   v
Run dbt transformations
   |
   v
Validate data
   |
   v
Send notification
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Airflow understands the relationship between these tasks.&lt;/p&gt;

&lt;p&gt;It knows that:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The transformation shouldn't run until the data has been loaded.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The validation shouldn't run until the transformation has completed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is where Airflow goes beyond a traditional cron job.&lt;/p&gt;




&lt;h1&gt;
  
  
  Airflow uses DAGs
&lt;/h1&gt;

&lt;p&gt;The workflows you create in Airflow are called &lt;strong&gt;DAGs&lt;/strong&gt;, which stands for &lt;strong&gt;Directed Acyclic Graph&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Don't let the name scare you.&lt;/p&gt;

&lt;p&gt;A DAG is essentially a definition of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What tasks should run, in what order, and under what conditions?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Extract Customers
       |
       v
Load Customers
       |
       v
Run dbt Customers
       |
       v
Validate Customers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also have multiple branches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;             Extract Data
                  |
          +-------+-------+
          |               |
          v               v
   Load Customers    Load Policies
          |               |
          v               v
   dbt Customers     dbt Policies
          |               |
          +-------+-------+
                  |
                  v
            Final Validation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Airflow understands these dependencies.&lt;/p&gt;




&lt;h1&gt;
  
  
  Airflow can schedule tasks like cron
&lt;/h1&gt;

&lt;p&gt;This is where the comparison comes from.&lt;/p&gt;

&lt;p&gt;You can tell Airflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Run every day at 2 AM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Run every hour
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Run every Monday
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So yes, Airflow can perform the scheduling role of cron.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Every day at 2 AM
        |
        v
Start DAG
        |
        v
Execute workflow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But scheduling is only one part of Airflow.&lt;/p&gt;




&lt;h1&gt;
  
  
  The biggest difference: Dependencies
&lt;/h1&gt;

&lt;p&gt;Imagine you have this workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Extract data
     |
     v
Load staging
     |
     v
Run dbt
     |
     v
Validate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't want &lt;code&gt;Run dbt&lt;/code&gt; to execute before &lt;code&gt;Load staging&lt;/code&gt; has completed.&lt;/p&gt;

&lt;p&gt;Airflow understands this dependency.&lt;/p&gt;

&lt;p&gt;Cron, on the other hand, would typically require you to manually coordinate the schedules.&lt;/p&gt;

&lt;p&gt;For example, you might end up with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2:00 AM -&amp;gt; Extract
2:30 AM -&amp;gt; Load staging
3:30 AM -&amp;gt; Run dbt
4:00 AM -&amp;gt; Validate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But what happens if extraction takes 2 hours instead of 30 minutes?&lt;/p&gt;

&lt;p&gt;Now your timing assumptions break.&lt;/p&gt;

&lt;p&gt;Airflow doesn't need you to rely solely on fixed times.&lt;/p&gt;

&lt;p&gt;It can say:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Extract completed successfully
        |
        v
Start Load
        |
        v
Load completed successfully
        |
        v
Start dbt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a much more reliable approach for complex workflows.&lt;/p&gt;




&lt;h1&gt;
  
  
  Airflow can retry failed tasks
&lt;/h1&gt;

&lt;p&gt;Another important feature is retries.&lt;/p&gt;

&lt;p&gt;Imagine your pipeline is loading data from an external database and the connection temporarily fails.&lt;/p&gt;

&lt;p&gt;Instead of requiring someone to manually restart everything, Airflow can be configured to retry the task.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Load data
   |
   X
Failed
   |
   v
Wait
   |
   v
Retry
   |
   v
Success
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can configure things such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Number of retries&lt;/li&gt;
&lt;li&gt;Retry delay&lt;/li&gt;
&lt;li&gt;Timeout&lt;/li&gt;
&lt;li&gt;Failure behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is particularly useful for data pipelines that depend on external systems.&lt;/p&gt;




&lt;h1&gt;
  
  
  Airflow provides monitoring
&lt;/h1&gt;

&lt;p&gt;Cron doesn't give you a sophisticated workflow monitoring interface.&lt;/p&gt;

&lt;p&gt;You might have logs scattered across servers and applications.&lt;/p&gt;

&lt;p&gt;Airflow provides a UI where you can see your workflows and tasks.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DAG: kehealth_elt

Task                     Status
------------------------------------
extract_claims            SUCCESS
load_claims               SUCCESS
dbt_staging_claims        SUCCESS
dbt_fct_claims            RUNNING
validate_claims           QUEUED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can immediately see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What has completed&lt;/li&gt;
&lt;li&gt;What is currently running&lt;/li&gt;
&lt;li&gt;What failed&lt;/li&gt;
&lt;li&gt;What is waiting&lt;/li&gt;
&lt;li&gt;How long a task has been running&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This becomes extremely valuable when workflows have many steps.&lt;/p&gt;




&lt;h1&gt;
  
  
  Airflow handles failure paths
&lt;/h1&gt;

&lt;p&gt;You can also define what should happen when something fails.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Run Task
                    |
              +-----+-----+
              |           |
           SUCCESS       FAIL
              |           |
              v           v
        Next Task       Retry
                          |
                    +-----+-----+
                    |           |
                 SUCCESS       FAIL
                    |           |
                    v           v
                 Continue     Alert
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you much more control than simply running a command from cron.&lt;/p&gt;




&lt;h1&gt;
  
  
  Airflow can orchestrate different technologies
&lt;/h1&gt;

&lt;p&gt;Another major advantage is that Airflow doesn't care that every task uses the same technology.&lt;/p&gt;

&lt;p&gt;One DAG could potentially orchestrate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Oracle
   |
   v
Python
   |
   v
SQL
   |
   v
dbt
   |
   v
API
   |
   v
Data validation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Extract data from Oracle
        |
        v
Run SQL transformation
        |
        v
Run dbt model
        |
        v
Call an API
        |
        v
Validate results
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Airflow acts as the coordinator.&lt;/p&gt;




&lt;h1&gt;
  
  
  How Airflow fits with ADF and dbt
&lt;/h1&gt;

&lt;p&gt;This becomes particularly interesting in modern enterprise data platforms.&lt;/p&gt;

&lt;p&gt;You might have an architecture like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 SOURCE
                   |
                   v
                 Oracle
                   |
                   v
                  ADF
                   |
                   v
              STAGING TABLES
                   |
                   v
                AIRFLOW
                   |
                   v
                  DBT
                   |
             +-----+-----+
             |           |
             v           v
        Dimensions      Facts
             |           |
             +-----+-----+
                   |
                   v
             Data Consumers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each technology has a different responsibility.&lt;/p&gt;

&lt;h3&gt;
  
  
  ADF
&lt;/h3&gt;

&lt;p&gt;ADF may handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connecting to source systems&lt;/li&gt;
&lt;li&gt;Extracting data&lt;/li&gt;
&lt;li&gt;Moving data&lt;/li&gt;
&lt;li&gt;Loading staging tables&lt;/li&gt;
&lt;li&gt;Initial orchestration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Get the data from A to B."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Airflow
&lt;/h3&gt;

&lt;p&gt;Airflow may handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scheduling&lt;/li&gt;
&lt;li&gt;Dependencies&lt;/li&gt;
&lt;li&gt;Workflow orchestration&lt;/li&gt;
&lt;li&gt;Retries&lt;/li&gt;
&lt;li&gt;Monitoring&lt;/li&gt;
&lt;li&gt;Error handling&lt;/li&gt;
&lt;li&gt;Coordinating different processes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Make sure the entire workflow happens in the right order."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  dbt
&lt;/h3&gt;

&lt;p&gt;dbt may handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data transformation&lt;/li&gt;
&lt;li&gt;Business logic&lt;/li&gt;
&lt;li&gt;SQL models&lt;/li&gt;
&lt;li&gt;Facts&lt;/li&gt;
&lt;li&gt;Dimensions&lt;/li&gt;
&lt;li&gt;Data tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Turn the raw/staged data into useful business data."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  A real-world example
&lt;/h1&gt;

&lt;p&gt;Imagine an insurance company wants to process claims every night.&lt;/p&gt;

&lt;p&gt;The workflow might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;              Oracle
                |
                v
               ADF
                |
                v
       Claims staging table
                |
                v
             Airflow
                |
                v
       Run dbt transformations
                |
                v
          fct_claims
                |
                v
          Data validation
                |
                v
            Reporting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At 2 AM, Airflow could start the workflow.&lt;/p&gt;

&lt;p&gt;ADF extracts the claims data from Oracle and loads it into staging.&lt;/p&gt;

&lt;p&gt;Once the staging process is complete, Airflow can trigger the dbt models.&lt;/p&gt;

&lt;p&gt;dbt transforms the staged data into the final fact and dimension tables.&lt;/p&gt;

&lt;p&gt;Airflow then runs validation tasks.&lt;/p&gt;

&lt;p&gt;If everything succeeds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SUCCESS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If something fails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FAILURE
   |
   +--&amp;gt; Retry
   |
   +--&amp;gt; Log error
   |
   +--&amp;gt; Notify team
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's much more sophisticated than:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2:00 AM
   |
   v
Run script
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  So, is Airflow a cron job?
&lt;/h1&gt;

&lt;p&gt;The best answer is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Airflow includes scheduling capabilities similar to cron, but it is not simply a cron job.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Think about the difference this way:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;Cron&lt;/th&gt;
&lt;th&gt;Airflow&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Schedule tasks&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Run scripts&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manage dependencies&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retry failed tasks&lt;/td&gt;
&lt;td&gt;Basic/manual&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workflow visualization&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Task monitoring&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complex workflows&lt;/td&gt;
&lt;td&gt;Difficult&lt;/td&gt;
&lt;td&gt;Designed for it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data pipelines&lt;/td&gt;
&lt;td&gt;Not its primary purpose&lt;/td&gt;
&lt;td&gt;Designed for it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dbt orchestration&lt;/td&gt;
&lt;td&gt;Possible&lt;/td&gt;
&lt;td&gt;Common use case&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failure handling&lt;/td&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;td&gt;Advanced&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  A simple analogy
&lt;/h1&gt;

&lt;p&gt;Imagine you're managing a restaurant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cron is an alarm clock.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"At 6 PM, start cooking."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Airflow is the restaurant manager.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"At 6 PM, start preparing the food. Once the ingredients are ready, start cooking. Don't serve until the food is ready. If the oven fails, retry. If the problem continues, alert the manager. Keep track of what has been completed."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the fundamental difference.&lt;/p&gt;




&lt;h1&gt;
  
  
  The mental model to remember
&lt;/h1&gt;

&lt;p&gt;If you're working with ADF, Airflow, and dbt, a useful mental model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ADF
  |
  | Move/ingest data
  v
STAGING
  |
  v
Airflow
  |
  | Orchestrate workflow
  v
dbt
  |
  | Transform data
  v
FACTS / DIMENSIONS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So instead of thinking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Airflow is just another cron job."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;think:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Cron schedules commands. Airflow schedules and orchestrates workflows."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That distinction becomes very important once your data pipelines start having multiple dependencies, retries, validations, long-running tasks, and different systems that need to work together.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>dataengineering</category>
      <category>datascience</category>
      <category>database</category>
    </item>
  </channel>
</rss>
