<?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>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 Laravel 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>
    <item>
      <title>Understanding Azure Data Factory Pipelines: From Source Systems to Production</title>
      <dc:creator>Sospeter Mong'are</dc:creator>
      <pubDate>Wed, 12 Aug 2026 11:26:21 +0000</pubDate>
      <link>https://dev.to/msnmongare/understanding-azure-data-factory-pipelines-from-source-systems-to-production-20g3</link>
      <guid>https://dev.to/msnmongare/understanding-azure-data-factory-pipelines-from-source-systems-to-production-20g3</guid>
      <description>&lt;p&gt;If you're new to Azure Data Factory (ADF), pipelines can initially feel complicated. You may see triggers, linked services, datasets, integration runtimes, copy activities, staging tables, transformations, Airflow, dbt, and several other components working together.&lt;/p&gt;

&lt;p&gt;The easiest way to understand ADF is to stop thinking of it as just a tool that "moves data" and instead think of it as part of a &lt;strong&gt;data journey&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A typical enterprise data flow 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;Source System
     |
     v
ADF Trigger
     |
     v
ADF Pipeline
     |
     v
Integration Runtime
     |
     v
Extract / Read Source
     |
     v
Load to Staging
     |
     v
Validation
     |
     v
Airflow
     |
     v
dbt Transformations
     |
     v
Fact / Dimension Tables
     |
     v
Reporting / Applications
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break down what happens at each stage.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Source System
&lt;/h2&gt;

&lt;p&gt;Everything starts with a source.&lt;/p&gt;

&lt;p&gt;The source is the system where the original data exists. It could be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Oracle&lt;/li&gt;
&lt;li&gt;SQL Server&lt;/li&gt;
&lt;li&gt;MySQL&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;REST APIs&lt;/li&gt;
&lt;li&gt;SFTP servers&lt;/li&gt;
&lt;li&gt;CSV files&lt;/li&gt;
&lt;li&gt;Azure Blob Storage&lt;/li&gt;
&lt;li&gt;Other enterprise applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, imagine an organization stores claims data in an Oracle database.&lt;/p&gt;

&lt;p&gt;ADF needs to connect to that Oracle database before it can extract anything.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Oracle Database
      |
      v
  ADF Pipeline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If ADF cannot establish a connection to Oracle, nothing else can happen.&lt;/p&gt;

&lt;p&gt;For example, you might encounter an error such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ORA-12170: Cannot connect.
TCP Connect timeout of 20000
for host 10.10.10.30 port 1521
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells you that the problem occurred while trying to establish the network connection to Oracle. It is not necessarily a problem with the SQL query or the target table.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The Trigger
&lt;/h2&gt;

&lt;p&gt;A trigger determines &lt;strong&gt;when a pipeline should run&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There are several common ways a pipeline can be triggered.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scheduled trigger
&lt;/h3&gt;

&lt;p&gt;A pipeline might run every night:&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
     |
     v
02:00 AM
     |
     v
Start pipeline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Manual trigger
&lt;/h3&gt;

&lt;p&gt;During development or testing, an engineer might manually start the pipeline.&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;ADF
 |
 +--&amp;gt; Trigger Now
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Event-based trigger
&lt;/h3&gt;

&lt;p&gt;A pipeline can also start when something happens.&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;New file uploaded
       |
       v
Event detected
       |
       v
ADF pipeline starts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, the trigger essentially answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;When should this pipeline start?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  3. The Pipeline
&lt;/h1&gt;

&lt;p&gt;Once the trigger fires, ADF creates a pipeline run.&lt;/p&gt;

&lt;p&gt;The pipeline defines &lt;strong&gt;what should happen&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A pipeline can contain multiple activities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Start
  |
  v
Extract data
  |
  v
Load staging table
  |
  v
Validate data
  |
  v
Complete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each activity performs a specific task.&lt;/p&gt;

&lt;p&gt;For example, a pipeline could contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Copy Activity&lt;/li&gt;
&lt;li&gt;Lookup Activity&lt;/li&gt;
&lt;li&gt;Stored Procedure Activity&lt;/li&gt;
&lt;li&gt;Data Flow&lt;/li&gt;
&lt;li&gt;If Condition&lt;/li&gt;
&lt;li&gt;ForEach&lt;/li&gt;
&lt;li&gt;Web Activity&lt;/li&gt;
&lt;li&gt;Execute Pipeline&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pipeline is essentially the workflow that connects these activities together.&lt;/p&gt;




&lt;h1&gt;
  
  
  4. The Integration Runtime
&lt;/h1&gt;

&lt;p&gt;This is one of the most important concepts to understand in ADF.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Integration Runtime (IR)&lt;/strong&gt; provides the infrastructure used by ADF to connect to data sources and move or process data.&lt;/p&gt;

&lt;p&gt;There are different types of Integration Runtime, but one that frequently matters in enterprise environments is the &lt;strong&gt;Self-hosted Integration Runtime&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Imagine your Oracle database is inside a private network:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;              Private Network

            Oracle Database
            10.10.10.30:1234
                   ^
                   |
                   |
          Self-hosted IR
                   ^
                   |
                   |
                  ADF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ADF may not be able to directly access the Oracle database.&lt;/p&gt;

&lt;p&gt;The Self-hosted Integration Runtime provides the connection between ADF and the private data source.&lt;/p&gt;

&lt;p&gt;This is why errors such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ORA-12170
TCP Connect timeout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;can be caused by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Network connectivity&lt;/li&gt;
&lt;li&gt;Firewall rules&lt;/li&gt;
&lt;li&gt;Oracle listener issues&lt;/li&gt;
&lt;li&gt;Incorrect host or port&lt;/li&gt;
&lt;li&gt;Self-hosted IR problems&lt;/li&gt;
&lt;li&gt;Routing issues&lt;/li&gt;
&lt;li&gt;Database availability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A useful troubleshooting question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Can the machine running the Self-hosted IR reach the Oracle server on port 1521?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example, from the Self-hosted IR machine, you might test:&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;Test-NetConnection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;10.10.10.30&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;1521&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the result is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TcpTestSucceeded : True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the machine can establish a TCP connection to the Oracle server.&lt;/p&gt;

&lt;p&gt;If it returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TcpTestSucceeded : False
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you have a connectivity problem that needs to be investigated before looking at SQL or transformation logic.&lt;/p&gt;




&lt;h1&gt;
  
  
  5. Linked Services
&lt;/h1&gt;

&lt;p&gt;A Linked Service defines &lt;strong&gt;how ADF connects to an external system&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Think of it as the connection configuration.&lt;/p&gt;

&lt;p&gt;For example, an Oracle Linked Service may contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host
Port
Service Name / SID
Username
Password
Integration Runtime
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Linked Service
      |
      +--&amp;gt; Oracle
      |
      +--&amp;gt; Host
      +--&amp;gt; Port
      +--&amp;gt; Credentials
      +--&amp;gt; Integration Runtime
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Linked Service answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How do I connect to this system?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  6. Datasets
&lt;/h1&gt;

&lt;p&gt;A Dataset represents the data you want to work with.&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;Oracle Linked Service
        |
        v
     Dataset
        |
        v
tableusers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is an important distinction between a Linked Service and a Dataset.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Linked Service:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How do I connect to the system?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Dataset:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What data do I want to access?&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;Linked Service
      |
      v
Oracle Database

Dataset
      |
      v
tableusers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  7. Copy Activity
&lt;/h1&gt;

&lt;p&gt;The Copy Activity is one of the most commonly used ADF activities.&lt;/p&gt;

&lt;p&gt;Its primary purpose is to &lt;strong&gt;move data from a source to a destination&lt;/strong&gt;.&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;Oracle
   |
   | Copy Activity
   v
SQL Server
   |
   v
Staging Table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Imagine the Oracle source contains:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;ADF could copy that data into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stgschema.stg_table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The flow becomes:&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 Copy Activity
   |
   v
stgschema.stg_table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is often the first major data movement step.&lt;/p&gt;




&lt;h1&gt;
  
  
  8. Staging
&lt;/h1&gt;

&lt;p&gt;Many enterprise data platforms use &lt;strong&gt;staging tables&lt;/strong&gt; as an intermediate landing area.&lt;/p&gt;

&lt;p&gt;Instead of transforming everything directly from the source, the organization first lands the data into staging.&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;Oracle
   |
   v
ADF
   |
   v
Staging
   |
   v
Transformation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The staging layer provides a controlled location where the incoming data can be stored before further processing.&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;Oracle:
tableusers

        |
        v

ADF

        |
        v

SQL Server:
stgschema.stg_table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  9. Transformation
&lt;/h1&gt;

&lt;p&gt;After data has been loaded into staging, it often needs to be transformed.&lt;/p&gt;

&lt;p&gt;For example, you may need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clean data&lt;/li&gt;
&lt;li&gt;Rename columns&lt;/li&gt;
&lt;li&gt;Remove duplicates&lt;/li&gt;
&lt;li&gt;Join tables&lt;/li&gt;
&lt;li&gt;Apply business rules&lt;/li&gt;
&lt;li&gt;Convert data types&lt;/li&gt;
&lt;li&gt;Create calculated fields&lt;/li&gt;
&lt;li&gt;Build dimensions&lt;/li&gt;
&lt;li&gt;Build fact tables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ADF itself can perform transformations using Data Flows and other activities.&lt;/p&gt;

&lt;p&gt;However, many modern data platforms separate &lt;strong&gt;data movement&lt;/strong&gt; from &lt;strong&gt;data transformation&lt;/strong&gt;.&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;ADF
 |
 v
Staging
 |
 v
Airflow
 |
 v
dbt
 |
 v
Fact / Dimension Tables
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where understanding the entire ecosystem becomes important.&lt;/p&gt;




&lt;h1&gt;
  
  
  10. Airflow
&lt;/h1&gt;

&lt;p&gt;In some enterprise environments, ADF is not responsible for the entire data pipeline.&lt;/p&gt;

&lt;p&gt;Instead, ADF handles part of the workflow, while &lt;strong&gt;Apache Airflow&lt;/strong&gt; handles downstream orchestration.&lt;/p&gt;

&lt;p&gt;You might have:&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
Staging Tables
   |
   v
Airflow
   |
   v
dbt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Airflow can coordinate what happens after the data has landed.&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;ADF finishes
     |
     v
Airflow starts
     |
     v
Run dbt models
     |
     v
Run validations
     |
     v
Complete pipeline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is why it is important to understand the dependency between ADF and Airflow in your organization's architecture.&lt;/p&gt;




&lt;h1&gt;
  
  
  11. dbt
&lt;/h1&gt;

&lt;p&gt;dbt is commonly used for the transformation layer.&lt;/p&gt;

&lt;p&gt;You might have staging tables such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stg_tbltableusers
stg_tblcustomer
stg_tblpolicy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then dbt transforms them into analytical models:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;staging
   |
   v
dbt
   |
   +--&amp;gt; dim_customer
   |
   +--&amp;gt; dim_policy
   |
   +--&amp;gt; fct_claims
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where raw or staged data becomes structured, business-ready data.&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;stg_tbltableusers
            |
            v
       dbt model
            |
            v
        fct_claims
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  12. Validation
&lt;/h1&gt;

&lt;p&gt;A good data pipeline should not simply say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The data moved successfully."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It should also verify that the data is correct.&lt;/p&gt;

&lt;p&gt;Validation might include checking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Record counts&lt;/li&gt;
&lt;li&gt;Null values&lt;/li&gt;
&lt;li&gt;Duplicate records&lt;/li&gt;
&lt;li&gt;Referential integrity&lt;/li&gt;
&lt;li&gt;Data types&lt;/li&gt;
&lt;li&gt;Business rules&lt;/li&gt;
&lt;li&gt;Source-to-target counts&lt;/li&gt;
&lt;/ul&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;Source count:
1,500,000

Target count:
1,500,000

Result:
PASS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But if you get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Source count:
1,500,000

Target count:
1,200,000

Result:
FAIL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the pipeline may need to stop or trigger an alert.&lt;/p&gt;




&lt;h1&gt;
  
  
  13. Dependencies
&lt;/h1&gt;

&lt;p&gt;ADF pipelines can contain dependencies between activities.&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;Extract Data
     |
     v
Load Staging
     |
     v
Validate
     |
     v
Continue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next activity only executes when the previous activity meets its dependency condition.&lt;/p&gt;

&lt;p&gt;You can also have different paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;             Extract
                |
        +-------+-------+
        |               |
     Success           Failure
        |               |
        v               v
     Continue        Error Flow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Understanding these dependencies is extremely important when troubleshooting.&lt;/p&gt;

&lt;p&gt;When a pipeline fails, don't simply look at the pipeline's overall status.&lt;/p&gt;

&lt;p&gt;Look at:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Which activity failed?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That usually tells you where the problem actually occurred.&lt;/p&gt;




&lt;h1&gt;
  
  
  14. Monitoring Long-Running Pipelines
&lt;/h1&gt;

&lt;p&gt;This is particularly important when you're working with enterprise pipelines.&lt;/p&gt;

&lt;p&gt;Not every pipeline takes five minutes.&lt;/p&gt;

&lt;p&gt;You might encounter pipelines that run for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10 minutes
1 hour
6 hours
12 hours
Several days
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If someone tells you:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Run this pipeline. It may take several days."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;you need to know how to monitor it.&lt;/p&gt;

&lt;p&gt;In ADF, you can go to:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monitor -&amp;gt; Pipeline runs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can inspect things such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pipeline
Status
Start Time
End Time
Duration
Trigger
Parameters
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can then drill down into the individual activities.&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;Activity              Status       Duration
------------------------------------------------
Extract Claims        Succeeded    2h 14m
Load Staging          Running      5h 32m
Validation            Pending      -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This helps you distinguish between:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The pipeline is still running"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The pipeline is actually stuck or failing."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That distinction is especially important with long-running data processes.&lt;/p&gt;




&lt;h1&gt;
  
  
  15. QA and Production
&lt;/h1&gt;

&lt;p&gt;In an enterprise environment, you generally don't make changes directly in production.&lt;/p&gt;

&lt;p&gt;A simplified deployment flow might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Development
     |
     v
QA ADF
     |
     | Test
     v
Production ADF
     |
     v
Production Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The QA environment allows you to test whether the pipeline behaves as expected.&lt;/p&gt;

&lt;p&gt;Once the changes are validated, they can be promoted to production according to the organization's deployment process.&lt;/p&gt;

&lt;p&gt;However, don't assume that because something works in QA it will automatically work in production.&lt;/p&gt;

&lt;p&gt;The environments can have different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Databases&lt;/li&gt;
&lt;li&gt;Credentials&lt;/li&gt;
&lt;li&gt;Integration Runtimes&lt;/li&gt;
&lt;li&gt;Network configurations&lt;/li&gt;
&lt;li&gt;Firewall rules&lt;/li&gt;
&lt;li&gt;Permissions&lt;/li&gt;
&lt;li&gt;Connection strings&lt;/li&gt;
&lt;li&gt;Parameters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Therefore, a pipeline can succeed in QA and fail in production because of an environmental difference.&lt;/p&gt;




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

&lt;p&gt;A typical enterprise data flow might therefore 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;                 SOURCE SYSTEM
                      |
                      v
                   Oracle
                      |
                      v
                 ADF Trigger
                      |
                      v
                 ADF Pipeline
                      |
                      v
            Integration Runtime
                      |
                      v
                Copy Activity
                      |
                      v
                  STAGING
                      |
                      v
                   Airflow
                      |
                      v
                    dbt
                      |
             +--------+--------+
             |                 |
             v                 v
       Dimensions            Facts
             |                 |
             +--------+--------+
                      |
                      v
             Reporting / Apps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important thing is that &lt;strong&gt;ADF may only be one part of the overall data platform&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;ADF might handle:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Source connectivity + extraction + data movement + initial orchestration&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Airflow might handle:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Workflow orchestration + scheduling + dependencies&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;dbt might handle:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Data transformation + business logic + analytical models&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  A Simple Mental Model
&lt;/h1&gt;

&lt;p&gt;If you're learning this ecosystem, remember these questions:&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;Question&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Trigger&lt;/td&gt;
&lt;td&gt;When should it run?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pipeline&lt;/td&gt;
&lt;td&gt;What should happen?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linked Service&lt;/td&gt;
&lt;td&gt;How do I connect?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dataset&lt;/td&gt;
&lt;td&gt;What data am I accessing?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integration Runtime&lt;/td&gt;
&lt;td&gt;Where/how does the connection execute?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copy Activity&lt;/td&gt;
&lt;td&gt;How do I move the data?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data Flow&lt;/td&gt;
&lt;td&gt;How do I transform data in ADF?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Staging&lt;/td&gt;
&lt;td&gt;Where does the incoming data land?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Airflow&lt;/td&gt;
&lt;td&gt;How do I orchestrate downstream processes?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dbt&lt;/td&gt;
&lt;td&gt;How do I transform the data into business models?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Validation&lt;/td&gt;
&lt;td&gt;Did the process produce correct data?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monitor&lt;/td&gt;
&lt;td&gt;What happened during execution?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The bigger picture is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SOURCE
  |
  | Oracle / API / Files / etc.
  v
ADF
  |
  | Integration Runtime
  v
EXTRACT
  |
  v
STAGING
  |
  v
AIRFLOW
  |
  v
DBT
  |
  v
FACTS / DIMENSIONS
  |
  v
REPORTING / CONSUMPTION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you understand this flow, ADF errors become much easier to reason about.&lt;/p&gt;

&lt;p&gt;For example, an error like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ORA-12170
TCP Connect timeout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;points you toward the &lt;strong&gt;connectivity layer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;An error like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ORA-00942
table or view does not exist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;points more toward the &lt;strong&gt;database object or permissions layer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A dbt &lt;code&gt;UNION&lt;/code&gt; column-count error points toward the &lt;strong&gt;transformation layer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And an Airflow DAG failure points toward the &lt;strong&gt;orchestration layer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The key skill is therefore not just learning how to click through ADF. It is learning to identify &lt;strong&gt;which layer of the data pipeline is failing and why&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Are Businesses Really Adopting AI? It Depends on What You Mean by "Adoption"</title>
      <dc:creator>Sospeter Mong'are</dc:creator>
      <pubDate>Mon, 10 Aug 2026 12:54:36 +0000</pubDate>
      <link>https://dev.to/msnmongare/are-businesses-really-adopting-ai-it-depends-on-what-you-mean-by-adoption-5f1a</link>
      <guid>https://dev.to/msnmongare/are-businesses-really-adopting-ai-it-depends-on-what-you-mean-by-adoption-5f1a</guid>
      <description>&lt;p&gt;&lt;a href="https://www.ibm.com/think/topics/artificial-intelligence" rel="noopener noreferrer"&gt;Artificial intelligence&lt;/a&gt; has become one of the biggest conversations in technology and business.&lt;/p&gt;

&lt;p&gt;Everywhere we look, businesses are being encouraged to "adopt AI", leaders are asking how AI can improve their operations, and employees are experimenting with tools such as ChatGPT, Microsoft Copilot, Claude, and AI-powered automation platforms.&lt;/p&gt;

&lt;p&gt;But there is an important question we often overlook:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What exactly do we mean when we say a business has "adopted AI"?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If AI adoption means building a sophisticated AI product from scratch, then relatively few businesses can claim to have adopted it.&lt;/p&gt;

&lt;p&gt;But if we consider the different ways AI can be applied across a business, the picture becomes very different.&lt;/p&gt;

&lt;p&gt;AI adoption exists on a spectrum.&lt;/p&gt;

&lt;p&gt;AI exists in six levels:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Everyday AI&lt;/li&gt;
&lt;li&gt;AI-powered workflow automation&lt;/li&gt;
&lt;li&gt;AI integration into existing systems&lt;/li&gt;
&lt;li&gt;AI-powered products&lt;/li&gt;
&lt;li&gt;AI agents and autonomous workflows&lt;/li&gt;
&lt;li&gt;Agentic AI for software development&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Using AI for everyday tasks
&lt;/h2&gt;

&lt;p&gt;The most basic level of AI adoption is simply using AI as a productivity tool.&lt;/p&gt;

&lt;p&gt;Employees can use tools such as ChatGPT or Copilot to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Draft emails and proposals&lt;/li&gt;
&lt;li&gt;Write and summarize reports&lt;/li&gt;
&lt;li&gt;Analyze documents&lt;/li&gt;
&lt;li&gt;Generate meeting notes&lt;/li&gt;
&lt;li&gt;Research topics&lt;/li&gt;
&lt;li&gt;Brainstorm ideas&lt;/li&gt;
&lt;li&gt;Translate or improve content&lt;/li&gt;
&lt;li&gt;Write and review code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This may not sound like "AI adoption" in the traditional sense, but it is.&lt;/p&gt;

&lt;p&gt;If an employee previously spent two hours preparing a report and can now complete the first draft in 30 minutes with AI assistance, the business is already benefiting from AI.&lt;/p&gt;

&lt;p&gt;The AI does not necessarily need to be integrated into the company's core systems for it to create value.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Automating business workflows with AI
&lt;/h2&gt;

&lt;p&gt;The next level goes beyond individual productivity.&lt;/p&gt;

&lt;p&gt;Businesses can integrate AI into workflows to automate repetitive tasks.&lt;/p&gt;

&lt;p&gt;For example, a company could use AI to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Categorize customer support requests&lt;/li&gt;
&lt;li&gt;Qualify sales leads&lt;/li&gt;
&lt;li&gt;Extract information from invoices&lt;/li&gt;
&lt;li&gt;Process application forms&lt;/li&gt;
&lt;li&gt;Summarize customer conversations&lt;/li&gt;
&lt;li&gt;Generate business reports&lt;/li&gt;
&lt;li&gt;Classify documents&lt;/li&gt;
&lt;li&gt;Route requests to the appropriate department&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consider a business receiving hundreds of customer emails every day.&lt;/p&gt;

&lt;p&gt;Instead of having employees manually read and categorize every message, an AI-powered workflow could identify the intent of each email, extract important information, categorize it, and route it to the right team.&lt;/p&gt;

&lt;p&gt;That is a much deeper form of AI adoption because AI has become part of the business process itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Integrating AI into existing products and systems
&lt;/h2&gt;

&lt;p&gt;Businesses don't always need to build completely new products to benefit from AI.&lt;/p&gt;

&lt;p&gt;They can add AI capabilities to systems they already use.&lt;/p&gt;

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

&lt;p&gt;A financial platform could introduce intelligent transaction analysis.&lt;/p&gt;

&lt;p&gt;An e-commerce platform could provide personalized recommendations.&lt;/p&gt;

&lt;p&gt;A customer service system could use AI to summarize conversations and suggest responses.&lt;/p&gt;

&lt;p&gt;An internal enterprise system could allow employees to search company documents using natural language rather than navigating through folders and databases.&lt;/p&gt;

&lt;p&gt;A business could also use AI to summarize large amounts of operational data and surface insights that would otherwise take employees hours to discover.&lt;/p&gt;

&lt;p&gt;In this case, AI becomes another capability within an existing product.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Building AI-powered products
&lt;/h2&gt;

&lt;p&gt;This is where many people traditionally think AI adoption begins.&lt;/p&gt;

&lt;p&gt;Instead of simply using AI internally, a company builds a product where AI is a fundamental part of the customer experience.&lt;/p&gt;

&lt;p&gt;Examples could include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI tutors&lt;/li&gt;
&lt;li&gt;Financial assistants&lt;/li&gt;
&lt;li&gt;Legal document analysis tools&lt;/li&gt;
&lt;li&gt;Personalized learning platforms&lt;/li&gt;
&lt;li&gt;AI-powered customer service platforms&lt;/li&gt;
&lt;li&gt;Intelligent business analytics tools&lt;/li&gt;
&lt;li&gt;AI content and marketing platforms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here, AI is not just helping employees work faster. It is part of what the company is actually selling to its customers.&lt;/p&gt;

&lt;p&gt;This is a significantly different level of adoption.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Using AI agents to handle business processes
&lt;/h2&gt;

&lt;p&gt;The conversation becomes even more interesting when we move from AI that generates responses to &lt;strong&gt;AI agents that can take actions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;An AI agent can potentially receive a goal, reason about what needs to be done, use available tools, and execute multiple steps to achieve that goal.&lt;/p&gt;

&lt;p&gt;For example, imagine a customer says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I made a payment but my order hasn't been updated."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An AI agent could potentially:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Identify the customer's account.&lt;/li&gt;
&lt;li&gt;Check the payment status.&lt;/li&gt;
&lt;li&gt;Check the order status.&lt;/li&gt;
&lt;li&gt;Determine whether the payment was successful.&lt;/li&gt;
&lt;li&gt;Update the relevant system if necessary.&lt;/li&gt;
&lt;li&gt;Respond to the customer.&lt;/li&gt;
&lt;li&gt;Escalate the issue if it cannot resolve it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The important difference is that the AI is not simply answering a question. It is interacting with business systems and taking actions.&lt;/p&gt;

&lt;p&gt;This is where AI starts moving from being a productivity tool to becoming an operational component of the business.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Using agentic AI for coding and software development
&lt;/h2&gt;

&lt;p&gt;There is another form of AI adoption that is particularly interesting for technology companies: &lt;strong&gt;agentic AI for software development&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Developers are increasingly using AI coding agents to do more than autocomplete code.&lt;/p&gt;

&lt;p&gt;These systems can help developers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understand unfamiliar codebases&lt;/li&gt;
&lt;li&gt;Implement new features&lt;/li&gt;
&lt;li&gt;Fix bugs&lt;/li&gt;
&lt;li&gt;Write tests&lt;/li&gt;
&lt;li&gt;Refactor existing code&lt;/li&gt;
&lt;li&gt;Investigate errors&lt;/li&gt;
&lt;li&gt;Review code&lt;/li&gt;
&lt;li&gt;Update documentation&lt;/li&gt;
&lt;li&gt;Run commands and tests&lt;/li&gt;
&lt;li&gt;Create pull requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This changes the software development workflow itself.&lt;/p&gt;

&lt;p&gt;A developer can give an AI coding agent a task such as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Investigate why customers are receiving duplicate payment notifications, identify the root cause, implement a fix, write tests, and prepare the changes for review."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent can potentially work across multiple files, inspect the existing code, run tests, and make the required changes.&lt;/p&gt;

&lt;p&gt;That does not mean developers are becoming irrelevant.&lt;/p&gt;

&lt;p&gt;It means the way developers work is changing.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Adoption Is Not Binary
&lt;/h2&gt;

&lt;p&gt;This is why I think asking whether businesses are "adopting AI" is too broad.&lt;/p&gt;

&lt;p&gt;AI adoption isn't simply:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adopted AI vs. Not adopted AI.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are different levels.&lt;/p&gt;

&lt;p&gt;A business might start with employees using ChatGPT for productivity.&lt;/p&gt;

&lt;p&gt;Then it could move to AI-powered workflow automation.&lt;/p&gt;

&lt;p&gt;Then integrate AI into its existing systems.&lt;/p&gt;

&lt;p&gt;Eventually, it could build AI-powered products or deploy AI agents that interact directly with its business processes.&lt;/p&gt;

&lt;p&gt;These are all forms of AI adoption, but they represent very different levels of maturity and impact.&lt;/p&gt;

&lt;h2&gt;
  
  
  So, Are we Adopting AI?
&lt;/h2&gt;

&lt;p&gt;I think the more useful question is not simply:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Are we adopting AI?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"At what level are we as a business/individual adopting AI, and where is that adoption creating measurable value?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some businesses may be using AI quietly for internal productivity.&lt;/p&gt;

&lt;p&gt;Others may be automating workflows.&lt;/p&gt;

&lt;p&gt;Some may be integrating AI into their existing products, while others are experimenting with AI agents and more autonomous systems.&lt;/p&gt;

&lt;p&gt;And some may still be trying to figure out where AI actually makes sense for their business.&lt;/p&gt;

&lt;p&gt;The absence of a flashy "AI-powered" product does not necessarily mean a company isn't adopting AI.&lt;/p&gt;

&lt;p&gt;AI adoption can happen behind the scenes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Question Should Be Value
&lt;/h2&gt;

&lt;p&gt;Ultimately, adopting AI should not be about adopting AI simply because everyone else is doing it.&lt;/p&gt;

&lt;p&gt;The better question is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What problem are we solving with AI?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Does it reduce operational costs?&lt;/p&gt;

&lt;p&gt;Does it save employees time?&lt;/p&gt;

&lt;p&gt;Does it improve customer experience?&lt;/p&gt;

&lt;p&gt;Does it increase revenue?&lt;/p&gt;

&lt;p&gt;Does it reduce errors?&lt;/p&gt;

&lt;p&gt;Does it help employees make better decisions?&lt;/p&gt;

&lt;p&gt;Does it allow a small team to accomplish what previously required a much larger team?&lt;/p&gt;

&lt;p&gt;If the answer is yes, then AI is already creating business value.&lt;/p&gt;

&lt;p&gt;The future of AI adoption will probably not be defined only by companies building large AI products.&lt;/p&gt;

&lt;p&gt;It will also be defined by thousands of businesses quietly integrating AI into the way they operate, serve customers, build software, analyze information, and make decisions.&lt;/p&gt;

&lt;p&gt;So, when we ask whether businesses are adopting AI, perhaps we should first define what "adoption" actually means.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The level of adoption looks very different depending on the scope.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>productivity</category>
      <category>automation</category>
      <category>ai</category>
    </item>
    <item>
      <title>Stop Trading Time for Money: A Software Engineer's Guide to Building Wealth Through Code</title>
      <dc:creator>Sospeter Mong'are</dc:creator>
      <pubDate>Fri, 31 Jul 2026 08:02:30 +0000</pubDate>
      <link>https://dev.to/msnmongare/stop-trading-time-for-money-a-software-engineers-guide-to-building-wealth-through-code-90n</link>
      <guid>https://dev.to/msnmongare/stop-trading-time-for-money-a-software-engineers-guide-to-building-wealth-through-code-90n</guid>
      <description>&lt;p&gt;One piece of advice every software engineer should think about is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Figure out how to use your skills to make money, not just trade time for money."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At first, it sounds like someone is saying jobs are bad. That's not the point.&lt;/p&gt;

&lt;p&gt;The real message is about leverage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Difference Between Working &lt;em&gt;In&lt;/em&gt; Software and Building With Software
&lt;/h2&gt;

&lt;p&gt;Most developers begin their careers by trading time for money.&lt;/p&gt;

&lt;p&gt;You write code for a company, complete freelance projects, or work as a consultant. Your income depends on the number of hours you work.&lt;/p&gt;

&lt;p&gt;There is absolutely nothing wrong with this. In fact, it's how most successful engineers develop their skills, gain experience, and build financial stability.&lt;/p&gt;

&lt;p&gt;The limitation is simple: if you stop working, the income usually stops too.&lt;/p&gt;

&lt;p&gt;Now imagine a different model.&lt;/p&gt;

&lt;p&gt;Instead of building the same solution repeatedly for different clients, you build it once and allow hundreds or even thousands of customers to use it.&lt;/p&gt;

&lt;p&gt;That's leverage.&lt;/p&gt;

&lt;h2&gt;
  
  
  You're Not Just a Developer. You're a Problem Solver.
&lt;/h2&gt;

&lt;p&gt;One of the biggest mistakes software engineers make is believing their value lies in writing code.&lt;/p&gt;

&lt;p&gt;It doesn't.&lt;/p&gt;

&lt;p&gt;Your real value lies in understanding problems well enough to build solutions that people are willing to pay for.&lt;/p&gt;

&lt;p&gt;The code is simply the tool.&lt;/p&gt;

&lt;p&gt;Every project you've worked on has exposed you to problems that businesses struggle with every day.&lt;/p&gt;

&lt;p&gt;Ask yourself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What tasks do companies repeat constantly?&lt;/li&gt;
&lt;li&gt;What processes waste the most time?&lt;/li&gt;
&lt;li&gt;What integrations are always painful?&lt;/li&gt;
&lt;li&gt;What manual work could be automated?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are business opportunities disguised as technical problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Productize What You Already Know
&lt;/h2&gt;

&lt;p&gt;Many developers chase the next trendy idea when their biggest opportunity is hiding in the work they're already doing.&lt;/p&gt;

&lt;p&gt;Suppose you've built payment integrations for multiple companies.&lt;/p&gt;

&lt;p&gt;Instead of manually implementing the same integrations over and over, you could build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A unified payments API&lt;/li&gt;
&lt;li&gt;A webhook management platform&lt;/li&gt;
&lt;li&gt;A payment reconciliation tool&lt;/li&gt;
&lt;li&gt;A payment testing environment&lt;/li&gt;
&lt;li&gt;A subscription billing platform&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rather than getting paid once for every integration, you create a product that many businesses can subscribe to.&lt;/p&gt;

&lt;p&gt;The same principle applies across every industry.&lt;/p&gt;

&lt;p&gt;If you've solved a problem more than three times, there's a good chance that problem deserves a product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build Software That Works While You Sleep
&lt;/h2&gt;

&lt;p&gt;The beauty of software is that it scales.&lt;/p&gt;

&lt;p&gt;Imagine you've built an inventory management system for a retail shop.&lt;/p&gt;

&lt;p&gt;If you sell it as custom software, every new client requires another implementation, customization, and deployment.&lt;/p&gt;

&lt;p&gt;But if you build it as a cloud-based SaaS platform, every new retailer simply signs up, configures their business, and starts paying a monthly subscription.&lt;/p&gt;

&lt;p&gt;The same product serves hundreds or thousands of businesses with minimal additional effort from you.&lt;/p&gt;

&lt;p&gt;That's the power of recurring revenue.&lt;/p&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How much should I charge for this project?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Start asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How many businesses have this problem?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Developers Have More Opportunities Than Ever
&lt;/h2&gt;

&lt;p&gt;Today's software engineers can build businesses around almost any technical skill. Here are a few examples:&lt;/p&gt;

&lt;h3&gt;
  
  
  SaaS Applications
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Inventory Management System&lt;/li&gt;
&lt;li&gt;Property Management Platform&lt;/li&gt;
&lt;li&gt;School Management System&lt;/li&gt;
&lt;li&gt;HR &amp;amp; Payroll Software&lt;/li&gt;
&lt;li&gt;Appointment Booking Platform&lt;/li&gt;
&lt;li&gt;CRM for SMEs&lt;/li&gt;
&lt;li&gt;Restaurant POS System&lt;/li&gt;
&lt;li&gt;Hotel Management Software&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  APIs for Developers
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Payment Aggregation API&lt;/li&gt;
&lt;li&gt;SMS &amp;amp; Email API&lt;/li&gt;
&lt;li&gt;WhatsApp Messaging API&lt;/li&gt;
&lt;li&gt;Identity Verification API&lt;/li&gt;
&lt;li&gt;Currency Exchange API&lt;/li&gt;
&lt;li&gt;Address Validation API&lt;/li&gt;
&lt;li&gt;Document Generation API&lt;/li&gt;
&lt;li&gt;Authentication &amp;amp; Single Sign-On API&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Automation Platforms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Invoice Processing Automation&lt;/li&gt;
&lt;li&gt;Employee Onboarding Automation&lt;/li&gt;
&lt;li&gt;Approval Workflow System&lt;/li&gt;
&lt;li&gt;Procurement Automation&lt;/li&gt;
&lt;li&gt;Customer Support Workflow Automation&lt;/li&gt;
&lt;li&gt;Marketing Campaign Automation&lt;/li&gt;
&lt;li&gt;Data Synchronization Platform&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  AI-Powered Tools
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;AI Customer Support Assistant&lt;/li&gt;
&lt;li&gt;AI Resume Screening Platform&lt;/li&gt;
&lt;li&gt;AI Meeting Notes Generator&lt;/li&gt;
&lt;li&gt;AI Document Search &amp;amp; Q&amp;amp;A&lt;/li&gt;
&lt;li&gt;AI Contract Analysis Tool&lt;/li&gt;
&lt;li&gt;AI Sales Assistant&lt;/li&gt;
&lt;li&gt;AI Knowledge Base Assistant&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Developer Tools
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;API Testing Platform&lt;/li&gt;
&lt;li&gt;Error Monitoring Service&lt;/li&gt;
&lt;li&gt;Database Migration Tool&lt;/li&gt;
&lt;li&gt;CI/CD Deployment Platform&lt;/li&gt;
&lt;li&gt;Log Management System&lt;/li&gt;
&lt;li&gt;Feature Flag Service&lt;/li&gt;
&lt;li&gt;Code Quality Analyzer&lt;/li&gt;
&lt;li&gt;API Documentation Generator&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Industry-Specific Software
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Healthcare Clinic Management&lt;/li&gt;
&lt;li&gt;Insurance Claims Platform&lt;/li&gt;
&lt;li&gt;Construction Project Management&lt;/li&gt;
&lt;li&gt;Agriculture Farm Management&lt;/li&gt;
&lt;li&gt;Logistics &amp;amp; Fleet Tracking&lt;/li&gt;
&lt;li&gt;Manufacturing ERP&lt;/li&gt;
&lt;li&gt;Legal Case Management&lt;/li&gt;
&lt;li&gt;Sacco Management System&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Integration Platforms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;ERP Integration Hub&lt;/li&gt;
&lt;li&gt;CRM Integration Platform&lt;/li&gt;
&lt;li&gt;Accounting Software Connectors&lt;/li&gt;
&lt;li&gt;E-commerce Integration Platform&lt;/li&gt;
&lt;li&gt;Data Synchronization Engine&lt;/li&gt;
&lt;li&gt;Enterprise API Gateway&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Monitoring &amp;amp; Security Tools
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;API Monitoring Dashboard&lt;/li&gt;
&lt;li&gt;Uptime Monitoring Service&lt;/li&gt;
&lt;li&gt;Security Vulnerability Scanner&lt;/li&gt;
&lt;li&gt;Audit Logging Platform&lt;/li&gt;
&lt;li&gt;Fraud Detection System&lt;/li&gt;
&lt;li&gt;Access Management Platform&lt;/li&gt;
&lt;li&gt;Compliance Monitoring Dashboard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The internet has made it possible for a single engineer to serve customers across the world.&lt;/p&gt;

&lt;p&gt;You no longer need thousands of employees to build a successful software company.&lt;/p&gt;

&lt;p&gt;Sometimes, all you need is one valuable product solving one painful problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your Job Is Not the Enemy
&lt;/h2&gt;

&lt;p&gt;There's a common misconception that every developer should quit their job and become an entrepreneur.&lt;/p&gt;

&lt;p&gt;That's terrible advice for most people.&lt;/p&gt;

&lt;p&gt;Your job provides more than a salary.&lt;/p&gt;

&lt;p&gt;It gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real-world experience&lt;/li&gt;
&lt;li&gt;Access to business problems&lt;/li&gt;
&lt;li&gt;Industry knowledge&lt;/li&gt;
&lt;li&gt;Professional relationships&lt;/li&gt;
&lt;li&gt;Capital to invest in your own ideas&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of your job as a paid research lab.&lt;/p&gt;

&lt;p&gt;Pay attention to the frustrations customers repeatedly face.&lt;/p&gt;

&lt;p&gt;Observe the repetitive work your team performs.&lt;/p&gt;

&lt;p&gt;Listen to the complaints people make every day.&lt;/p&gt;

&lt;p&gt;Hidden inside those frustrations are business opportunities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Think Like a Business Owner
&lt;/h2&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"What app should I build?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"What expensive problem can I solve repeatedly?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"How do I get more freelance clients?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Can I build a product that hundreds of clients can use?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"What programming language should I learn next?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"What industry do I understand well enough to improve with software?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those questions lead to businesses, not just careers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Journey
&lt;/h2&gt;

&lt;p&gt;Most successful software entrepreneurs don't start by building billion-dollar companies.&lt;/p&gt;

&lt;p&gt;They start by solving one real problem.&lt;/p&gt;

&lt;p&gt;Then they solve it again.&lt;/p&gt;

&lt;p&gt;Then they turn that solution into a product.&lt;/p&gt;

&lt;p&gt;The journey often looks like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer → Problem Solver → Product Builder → Business Owner&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The goal isn't to stop trading time for money overnight.&lt;/p&gt;

&lt;p&gt;The goal is to gradually build assets that generate value beyond your own working hours.&lt;/p&gt;

&lt;p&gt;Because the most powerful thing about being a software engineer isn't your ability to write code.&lt;/p&gt;

&lt;p&gt;It's your ability to create systems that continue delivering value long after you've finished writing them.&lt;/p&gt;

</description>
      <category>software</category>
      <category>softwareengineering</category>
      <category>beginners</category>
      <category>career</category>
    </item>
    <item>
      <title>Your AI Agent Is Not an Engineer</title>
      <dc:creator>Sospeter Mong'are</dc:creator>
      <pubDate>Mon, 27 Jul 2026 08:44:59 +0000</pubDate>
      <link>https://dev.to/msnmongare/your-ai-agent-is-not-an-engineer-435k</link>
      <guid>https://dev.to/msnmongare/your-ai-agent-is-not-an-engineer-435k</guid>
      <description>&lt;h3&gt;
  
  
  Building Reliable Software in the Age of Agentic Coding
&lt;/h3&gt;

&lt;p&gt;A few weeks into using coding agents seriously, not for autocomplete, but for actually driving multi-file changes, wiring up integrations, standing up agent pipelines, I noticed a pattern. The code that came out looked right. It compiled, it ran, the demo worked. And then, days later, something broke in a way that made it obvious the agent had never actually understood the system it was working in. It had pattern-matched its way to a plausible-looking answer.&lt;/p&gt;

&lt;p&gt;That's the core thing to internalize: a coding agent is a very good pattern completer, not an engineer. Engineers hold intent, constraints, failure modes, and tradeoffs in their heads at the same time. Agents hold a context window. Confusing the two is where reliability problems start.&lt;/p&gt;

&lt;h2&gt;
  
  
  What coding agents are actually good at
&lt;/h2&gt;

&lt;p&gt;To be fair to the tools, they've earned their place in the workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Boilerplate and scaffolding.&lt;/strong&gt; CRUD endpoints, config files, test skeletons, repetitive glue code across a codebase.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Translating a clear spec into a first draft.&lt;/strong&gt; If you can describe the shape of what you want precisely, an agent will get you 80% of the way there fast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Working across unfamiliar syntax.&lt;/strong&gt; An agent that's seen a thousand times more Terraform or SQL dialects than you have can save real research time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mechanical refactors.&lt;/strong&gt; Rename this across 40 files, migrate this API surface, update this dependency's usage pattern everywhere it appears.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are real productivity gains. The mistake is extrapolating from "good at boilerplate" to "can be trusted with architecture."&lt;/p&gt;

&lt;h2&gt;
  
  
  Where they fail
&lt;/h2&gt;

&lt;p&gt;The failures aren't random, they cluster in predictable places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Silent scope narrowing.&lt;/strong&gt; Ask an agent to "handle errors properly" and it will handle the errors it can see in the current file, not the ones three layers up the call stack that your actual production traffic hits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confident wrongness.&lt;/strong&gt; Agents don't have a strong signal for "I'm not sure." They'll write a Daraja API callback handler that looks idiomatic and is subtly wrong about idempotency, with the same confidence as one that's correct.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local optimization, global blindness.&lt;/strong&gt; An agent fixing a bug in one service has no model of the three other services that depend on the behavior it just changed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security as an afterthought.&lt;/strong&gt; Left to their own devices, agents will happily hardcode a secret, skip input validation, or generate SQL that's one crafted string away from an injection, because none of that breaks the immediate task.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test theater.&lt;/strong&gt; Agents will write tests that pass, sometimes because they wrote the test to match the implementation's actual (wrong) behavior rather than the intended one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is a knock on the tools. It's what you'd expect from a system optimizing for "produce plausible next tokens" rather than "hold this system accountable over time."&lt;/p&gt;

&lt;h2&gt;
  
  
  Why fundamentals matter more, not less
&lt;/h2&gt;

&lt;p&gt;The tempting narrative is that agentic coding makes engineering fundamentals less relevant, that if the AI writes the code, you don't need to understand it as deeply. The opposite is true.&lt;/p&gt;

&lt;p&gt;When you're the one writing every line, your understanding is forced on you by the act of typing it. When an agent writes it, understanding becomes optional, and skipping it is exactly how systems accumulate debt nobody can explain later. The engineer's job shifts from &lt;em&gt;writing&lt;/em&gt; correct code to &lt;em&gt;specifying, verifying, and bounding&lt;/em&gt; correct code. That's arguably a harder skill, not an easier one. It requires the same grounding in data modeling, failure modes, concurrency, and system boundaries, just applied at review time instead of write time.&lt;/p&gt;

&lt;p&gt;If you don't already know what a race condition looks like, you won't catch one in a 400-line diff an agent generated in nine seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context management
&lt;/h2&gt;

&lt;p&gt;Most agent failures I've traced back aren't reasoning failures, they're context failures. The agent didn't have the right information in front of it, so it filled the gap with something plausible.&lt;/p&gt;

&lt;p&gt;Practical habits that help:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep the agent's working context scoped to the smallest slice of the system that's actually relevant. A bloated context window doesn't mean better decisions, it often means diluted attention.&lt;/li&gt;
&lt;li&gt;Externalize constraints instead of assuming they're inferred: rate limits, idempotency requirements, data retention rules, compliance boundaries. Say them explicitly, every time, rather than trusting the agent remembers them from three turns ago.&lt;/li&gt;
&lt;li&gt;Treat the agent's memory of your codebase as a snapshot, not a live view. If you changed something in another session, don't assume it knows.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Testing AI-generated code
&lt;/h2&gt;

&lt;p&gt;Agent-written code needs a different testing posture than human-written code, because the failure distribution is different. Where a human engineer tends to make errors of omission under time pressure, an agent tends to make errors of &lt;em&gt;false confidence&lt;/em&gt;, it will implement something that looks complete but quietly diverges from the spec.&lt;/p&gt;

&lt;p&gt;That argues for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Writing the test cases yourself, or at minimum reviewing them line by line, rather than letting the agent write both the implementation and its own tests.&lt;/li&gt;
&lt;li&gt;Testing the boundaries the agent wasn't explicitly told about, the empty list, the network timeout, the malformed webhook payload, since those are exactly where pattern-completion breaks down.&lt;/li&gt;
&lt;li&gt;Running generated code against real, messy data before trusting it against clean fixtures.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Security risks
&lt;/h2&gt;

&lt;p&gt;Agentic coding introduces a security surface that didn't exist five years ago, and it's not hypothetical:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Secrets leakage.&lt;/strong&gt; Agents pulling credentials into logs, commits, or prompts they shouldn't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency sprawl.&lt;/strong&gt; An agent reaching for a package to solve a small problem, without anyone evaluating its maintenance status or supply-chain risk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Injection through generated code.&lt;/strong&gt; String-built queries, unsanitized inputs, permissive CORS, the classic mistakes, just produced faster and in more places at once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt injection in agent pipelines.&lt;/strong&gt; If your agent reads external content (a webpage, a customer message, a file) as part of its task, that content can carry instructions the agent will follow unless you've explicitly hardened against it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is solved by "the agent is usually careful." It's solved by treating every agent-touched surface as untrusted until reviewed, the same discipline you'd apply to a contributor you'd never met.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reviewing agent decisions
&lt;/h2&gt;

&lt;p&gt;Code review of agent output has to be different from code review of a colleague's PR, because the failure mode is different. A colleague's mistake usually comes with a reasoning trail you can interrogate. An agent's mistake often doesn't, it just produced the output.&lt;/p&gt;

&lt;p&gt;That means review has to reconstruct the "why," not just check the "what":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ask the agent to explain its reasoning &lt;em&gt;before&lt;/em&gt; accepting a nontrivial change, and read the explanation critically, not as a formality.&lt;/li&gt;
&lt;li&gt;Diff against intent, not just against the previous version. Does this change actually do what was asked, or does it do something adjacent that happens to pass the visible tests?&lt;/li&gt;
&lt;li&gt;Be especially suspicious of changes that touch more files than the task seemed to require. That's often where an agent "helpfully" over-scoped.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Keeping humans in control
&lt;/h2&gt;

&lt;p&gt;The workflows that hold up under real production load share a shape: agents propose, humans dispose. Concretely, that looks like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agents work in tightly scoped tasks with clear, checkable success criteria, not open-ended "improve this system" mandates.&lt;/li&gt;
&lt;li&gt;Every agent-generated change goes through the same review gates as human-generated change, with no shortcut for "the AI wrote it so it's probably fine."&lt;/li&gt;
&lt;li&gt;Irreversible or high-blast-radius actions, deployments, schema migrations, anything touching payments or customer data, require an explicit human approval step, not just an agent's self-assessed confidence.&lt;/li&gt;
&lt;li&gt;Someone on the team remains accountable for understanding the system end to end, even if they didn't type most of the code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The bottom line
&lt;/h2&gt;

&lt;p&gt;Coding agents are a genuine force multiplier for the parts of engineering that were always mechanical. They are not a substitute for the parts that were never mechanical: judgment about tradeoffs, understanding of failure modes, and accountability for what ships. Building reliable software with agentic tools doesn't mean trusting them more as they get better. It means getting sharper about exactly where the boundary of their competence sits, and refusing to let that boundary quietly drift because the code that came out looked right.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>agents</category>
      <category>ai</category>
      <category>software</category>
    </item>
    <item>
      <title>How to Check Whether Your Git Branch Is Up to Date With QA, Master, and Your Remote Branch</title>
      <dc:creator>Sospeter Mong'are</dc:creator>
      <pubDate>Tue, 21 Jul 2026 12:31:15 +0000</pubDate>
      <link>https://dev.to/msnmongare/how-to-check-whether-your-git-branch-is-up-to-date-with-qa-master-and-your-remote-branch-5253</link>
      <guid>https://dev.to/msnmongare/how-to-check-whether-your-git-branch-is-up-to-date-with-qa-master-and-your-remote-branch-5253</guid>
      <description>&lt;p&gt;When working with Git in a team environment, especially with a workflow such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;feature branch -&amp;gt; qa -&amp;gt; master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;it is important to know whether your branch is up to date before creating or completing a Pull Request.&lt;/p&gt;

&lt;p&gt;A common mistake is assuming that this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your branch is up to date with 'origin/feature/smongare'.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;means your branch is also up to date with &lt;code&gt;qa&lt;/code&gt; and &lt;code&gt;master&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It does not.&lt;/p&gt;

&lt;p&gt;That message only confirms that your &lt;strong&gt;local feature branch&lt;/strong&gt; is synchronized with its &lt;strong&gt;remote feature branch&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let's look at how to properly check everything.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Understanding Local and Remote Branches
&lt;/h2&gt;

&lt;p&gt;Suppose you have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;feature/smongare
qa
master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your local repository may 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;feature/smongare
qa
master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the remote repository has separate branches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;origin/feature/smongare
origin/qa
origin/master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can visualize the relationship 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;Local Repository                 Remote Repository

feature/smongare  &amp;lt;----------&amp;gt;  origin/feature/smongare
qa                &amp;lt;----------&amp;gt;  origin/qa
master            &amp;lt;----------&amp;gt;  origin/master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your local branches may not automatically know about the latest changes made remotely.&lt;/p&gt;

&lt;p&gt;That is why the first step is always to fetch the latest information.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Fetch the Latest Changes
&lt;/h2&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git fetch origin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does not modify your working files or merge anything into your current branch.&lt;/p&gt;

&lt;p&gt;It simply updates your local knowledge of the remote branches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;origin/feature/smongare
origin/qa
origin/master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git fetch origin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;your Git repository knows the latest commit positions of the remote branches.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Check Whether Your Feature Branch Is Up to Date With QA
&lt;/h2&gt;

&lt;p&gt;Suppose you are currently on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;feature/smongare
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git rev-list &lt;span class="nt"&gt;--left-right&lt;/span&gt; &lt;span class="nt"&gt;--count&lt;/span&gt; origin/qa...feature/smongare
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3 5
&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;3 commits  -&amp;gt; exist in QA but not in your feature branch
5 commits  -&amp;gt; exist in your feature branch but not in QA
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In other words:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;QA:               A - B - C - D - E
                         \
Feature Branch:          F - G - H - I - J
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your feature branch is missing 3 commits from &lt;code&gt;qa&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You should probably bring the latest changes from &lt;code&gt;qa&lt;/code&gt; into your feature branch before completing your PR.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Understanding the Output
&lt;/h2&gt;

&lt;p&gt;The command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git rev-list &lt;span class="nt"&gt;--left-right&lt;/span&gt; &lt;span class="nt"&gt;--count&lt;/span&gt; origin/qa...feature/smongare
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;returns two numbers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;commits in QA&amp;gt; &amp;lt;commits in feature branch&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  Result:
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;p&gt;Your feature branch contains everything currently in &lt;code&gt;qa&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Your feature branch has 5 additional commits.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;QA:      0 missing commits
Feature: 5 additional commits
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is generally what you expect when developing a feature.&lt;/p&gt;




&lt;h3&gt;
  
  
  Result:
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;p&gt;Your feature branch is behind &lt;code&gt;qa&lt;/code&gt; by 3 commits.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;QA:      3 commits your branch does not have
Feature: 5 commits QA does not have
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should consider updating your feature branch.&lt;/p&gt;




&lt;h3&gt;
  
  
  Result:
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;p&gt;Your feature branch is behind &lt;code&gt;qa&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It has no commits that are not already in &lt;code&gt;qa&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Check Your Feature Branch Against Master
&lt;/h2&gt;

&lt;p&gt;You can perform the same check against &lt;code&gt;master&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git rev-list &lt;span class="nt"&gt;--left-right&lt;/span&gt; &lt;span class="nt"&gt;--count&lt;/span&gt; origin/master...feature/smongare
&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;0 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means your feature branch contains all commits currently in &lt;code&gt;master&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;However, if your Pull Request is targeting &lt;code&gt;qa&lt;/code&gt;, then &lt;code&gt;qa&lt;/code&gt; is the more important branch to check before completing the PR.&lt;/p&gt;

&lt;p&gt;Your typical workflow might be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;feature/smongare
        |
        v
       QA
        |
        v
     MASTER
&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;Before PR to QA:
Check feature/smongare against qa

Before PR to master:
Check qa against master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  6. Check Whether Your Local Feature Branch Matches the Remote Feature Branch
&lt;/h2&gt;

&lt;p&gt;Your &lt;code&gt;git status&lt;/code&gt; already tells you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your branch is up to date with 'origin/feature/smongare'.
&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;Local feature/smongare
        =
Remote origin/feature/smongare
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also verify it manually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git rev-list &lt;span class="nt"&gt;--left-right&lt;/span&gt; &lt;span class="nt"&gt;--count&lt;/span&gt; origin/feature/smongare...feature/smongare
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

&lt;/div&gt;



&lt;p&gt;then both branches are identical.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. See the Actual Commits Missing From Your Feature Branch
&lt;/h2&gt;

&lt;p&gt;Numbers are useful, but sometimes you want to see the actual commits.&lt;/p&gt;

&lt;p&gt;To see commits that exist in &lt;code&gt;qa&lt;/code&gt; but not in your feature branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git log &lt;span class="nt"&gt;--oneline&lt;/span&gt; feature/smongare..origin/qa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a1b2c3d Fix Airflow connection
e4f5g6h Update dbt configuration
i7j8k9l Add new environment variables
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are commits that your feature branch does not currently contain.&lt;/p&gt;

&lt;p&gt;To see commits in &lt;code&gt;master&lt;/code&gt; that your feature branch does not have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git log &lt;span class="nt"&gt;--oneline&lt;/span&gt; feature/smongare..origin/master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To see commits in your feature branch that are not in &lt;code&gt;qa&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git log &lt;span class="nt"&gt;--oneline&lt;/span&gt; origin/qa..feature/smongare
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  8. A Complete Check for All Branches
&lt;/h2&gt;

&lt;p&gt;You can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git fetch origin

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Compared with QA:"&lt;/span&gt;
git rev-list &lt;span class="nt"&gt;--left-right&lt;/span&gt; &lt;span class="nt"&gt;--count&lt;/span&gt; origin/qa...feature/smongare

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Compared with MASTER:"&lt;/span&gt;
git rev-list &lt;span class="nt"&gt;--left-right&lt;/span&gt; &lt;span class="nt"&gt;--count&lt;/span&gt; origin/master...feature/smongare

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Compared with remote feature branch:"&lt;/span&gt;
git rev-list &lt;span class="nt"&gt;--left-right&lt;/span&gt; &lt;span class="nt"&gt;--count&lt;/span&gt; origin/feature/smongare...feature/smongare
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Compared with QA:
0 5

Compared with MASTER:
0 5

Compared with remote feature branch:
0 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Your feature branch contains all the latest changes from &lt;code&gt;qa&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Your feature branch contains all the latest changes from &lt;code&gt;master&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Your local feature branch is identical to the remote feature branch&lt;/li&gt;
&lt;li&gt;Your feature branch has 5 commits that have not yet been merged into &lt;code&gt;qa&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  9. The Most Important Check Before a PR
&lt;/h2&gt;

&lt;p&gt;Suppose your Pull Request is:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The most important thing is to ensure that your branch works with the latest version of &lt;code&gt;qa&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can merge the latest &lt;code&gt;qa&lt;/code&gt; changes into your feature branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git fetch origin

git checkout feature/smongare

git merge origin/qa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If Git reports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Already up to date.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then your feature branch already contains the latest changes from &lt;code&gt;qa&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If there are conflicts, Git will show them.&lt;/p&gt;

&lt;p&gt;You can then:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Resolve the conflicts&lt;/li&gt;
&lt;li&gt;Stage the resolved files
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Complete the merge
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git commit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Push the updated feature branch
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push origin feature/smongare
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your Azure DevOps Pull Request will then be updated with the latest changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Key Lesson
&lt;/h2&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your branch is up to date with 'origin/feature/smongare'.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;only tells you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;local feature branch = remote feature branch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; tell you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;feature branch = qa
&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;feature branch = master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To properly check your branch relationships:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git fetch origin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then compare branches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git rev-list &lt;span class="nt"&gt;--left-right&lt;/span&gt; &lt;span class="nt"&gt;--count&lt;/span&gt; origin/qa...feature/smongare
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git rev-list &lt;span class="nt"&gt;--left-right&lt;/span&gt; &lt;span class="nt"&gt;--count&lt;/span&gt; origin/master...feature/smongare
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And before completing a Pull Request to &lt;code&gt;qa&lt;/code&gt;, the safest approach is usually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git merge origin/qa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one of the simplest ways to discover potential conflicts before Azure DevOps blocks or complicates your Pull Request.&lt;/p&gt;

</description>
      <category>git</category>
      <category>github</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Airflow 3 Simple Auth Manager</title>
      <dc:creator>Sospeter Mong'are</dc:creator>
      <pubDate>Wed, 15 Jul 2026 10:03:46 +0000</pubDate>
      <link>https://dev.to/msnmongare/airflow-3-simple-auth-manager-19k3</link>
      <guid>https://dev.to/msnmongare/airflow-3-simple-auth-manager-19k3</guid>
      <description>&lt;p&gt;If you've recently upgraded to &lt;a href="https://airflow.apache.org/" rel="noopener noreferrer"&gt;Apache Airflow&lt;/a&gt; 3 and logged into the webserver, you may have noticed something different about how login and permissions work. Gone is the Flask-AppBuilder (FAB) based login system that powered Airflow 2. In its place is the &lt;strong&gt;Simple Auth Manager&lt;/strong&gt;, the new default authentication and authorization system for Airflow 3.&lt;/p&gt;

&lt;p&gt;This guide explains what Apache Airflow is for readers who are new to it, what an auth manager does, how the Simple Auth Manager works, how to configure it, and what you need to know before relying on it, especially in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Apache Airflow?
&lt;/h2&gt;

&lt;p&gt;If you're brand new to Airflow, here's the short version: &lt;a href="https://airflow.apache.org/" rel="noopener noreferrer"&gt;Apache Airflow&lt;/a&gt; is an open-source platform used to author, schedule, and monitor workflows. A "workflow" in Airflow is called a &lt;strong&gt;DAG&lt;/strong&gt; (Directed Acyclic Graph), which is essentially a series of tasks with dependencies between them, written as Python code. Data engineers commonly use Airflow to orchestrate things like ETL pipelines, machine learning training jobs, and routine data syncs between systems.&lt;/p&gt;

&lt;p&gt;Because Airflow has a web-based UI where you can view, trigger, and monitor these workflows, it needs a way to control who can log in and what they're allowed to do once they're in. That's where an &lt;strong&gt;auth manager&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is an Auth Manager?
&lt;/h2&gt;

&lt;p&gt;An auth manager is the component in Airflow responsible for two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Authentication&lt;/strong&gt; — verifying who a user is (usually via username and password, or an external identity provider)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authorization&lt;/strong&gt; — determining what that user is allowed to see and do once logged in&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Airflow's auth manager system is pluggable, meaning you can swap in different implementations depending on your needs. You could use one built around your company's identity provider, for example, or write a custom one. The &lt;a href="https://airflow.apache.org/docs/apache-airflow/stable/core-concepts/auth-manager/index.html" rel="noopener noreferrer"&gt;Auth Manager documentation&lt;/a&gt; covers the full interface and the available options.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is the Simple Auth Manager?
&lt;/h2&gt;

&lt;p&gt;The Simple Auth Manager is the auth manager that comes by default in Airflow 3. As its name suggests, its logic and implementation are intentionally simple. It replaces FabAuthManager, the default in Airflow 2, as part of a broader effort to remove the FAB dependency from Airflow's core and make the authentication layer pluggable.&lt;/p&gt;

&lt;p&gt;The intended usage of the Simple Auth Manager is only for development and testing purposes, and &lt;strong&gt;it should not be used in production&lt;/strong&gt;. It's deliberately limited in scope: it doesn't support flexible role definitions or custom mappings between roles and capabilities. If you need a production-grade authentication setup, Airflow supports other pluggable auth managers, including ones built around cloud identity providers, and you can write your own using the &lt;a href="https://airflow.apache.org/docs/apache-airflow/stable/core-concepts/auth-manager/index.html#writing-your-own-auth-manager" rel="noopener noreferrer"&gt;auth manager interface&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The full official reference for this feature lives in the &lt;a href="https://airflow.apache.org/docs/apache-airflow/stable/core-concepts/auth-manager/simple/index.html" rel="noopener noreferrer"&gt;Simple Auth Manager documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How User Management Works
&lt;/h2&gt;

&lt;p&gt;Unlike FAB, which stored users and roles in Airflow's metadata database, the Simple Auth Manager is fully config-controlled and doesn't touch the database at all. Users are defined directly in your &lt;a href="https://airflow.apache.org/docs/apache-airflow/stable/configurations-ref.html" rel="noopener noreferrer"&gt;Airflow configuration file&lt;/a&gt; (&lt;code&gt;airflow.cfg&lt;/code&gt;), or via environment variables.&lt;/p&gt;

&lt;p&gt;Here's the basic format:&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="nn"&gt;[core]&lt;/span&gt;
&lt;span class="py"&gt;simple_auth_manager_users&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"bob:admin,peter:viewer"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The list of users is comma-separated, and each user is a username/role pair separated by a colon. In the example above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;bob&lt;/strong&gt; is assigned the &lt;strong&gt;admin&lt;/strong&gt; role&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;peter&lt;/strong&gt; is assigned the &lt;strong&gt;viewer&lt;/strong&gt; role&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Passwords Are Auto-Generated
&lt;/h3&gt;

&lt;p&gt;You'll notice the configuration only specifies usernames and roles, not passwords. That's intentional. Passwords are auto-generated for each user and printed in the webserver logs. Once generated, they're saved to a file set by &lt;code&gt;core.simple_auth_manager_passwords_file&lt;/code&gt;, which defaults to &lt;code&gt;$AIRFLOW_HOME/simple_auth_manager_passwords.json.generated&lt;/code&gt;. You can open that file and read (or manually update) the passwords if needed.&lt;/p&gt;

&lt;p&gt;If you're running Airflow through &lt;a href="https://airflow.apache.org/docs/apache-airflow/stable/start.html" rel="noopener noreferrer"&gt;Breeze&lt;/a&gt; (Airflow's local development environment) for local development, two users come predefined out of the box: &lt;code&gt;admin&lt;/code&gt; and &lt;code&gt;viewer&lt;/code&gt;, each with a password matching their username.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A common gotcha:&lt;/strong&gt; setting environment variables like &lt;code&gt;AIRFLOW__SIMPLE_AUTH_MANAGER__USERNAME&lt;/code&gt; and &lt;code&gt;PASSWORD&lt;/code&gt; won't reliably let you pre-seed a specific password. Since Airflow generates a random password on startup by default, this trips up a lot of people trying to force a known password this way. If you need deterministic credentials, editing the generated passwords file directly after startup is currently the more dependable route.&lt;/p&gt;

&lt;h2&gt;
  
  
  Roles and Permissions
&lt;/h2&gt;

&lt;p&gt;The Simple Auth Manager doesn't let you create custom roles or fine-tune permission mappings. There's no option to manage roles and permissions; they're defined as part of the implementation and can't be modified. There are four fixed roles:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Permissions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Viewer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Read-only access to Dags, assets, and pools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;User&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Viewer permissions, plus full edit, create, and delete permissions on Dags&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Op&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;User permissions, plus full permissions on pools, assets, config, connections, and variables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Admin&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;All permissions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is a deliberate tradeoff. Rather than giving you the granular role-based access control (RBAC) that FAB offered, the Simple Auth Manager sticks to four broad tiers that are easy to reason about and hard to misconfigure, at the cost of flexibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Team Support
&lt;/h2&gt;

&lt;p&gt;If you're running Airflow for multiple teams sharing a single environment, the Simple Auth Manager supports basic team-based resource isolation. When &lt;a href="https://airflow.apache.org/docs/apache-airflow/stable/core-concepts/multi-team.html" rel="noopener noreferrer"&gt;multi-team mode&lt;/a&gt; is enabled, users can be associated with one or more teams, and teams restrict which resources (DAGs, connections, variables, pools) a user can access.&lt;/p&gt;

&lt;p&gt;Enable it with:&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="nn"&gt;[core]&lt;/span&gt;
&lt;span class="py"&gt;multi_team&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then assign teams to users by adding a third colon-separated field, with multiple teams separated by a pipe character:&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="nn"&gt;[core]&lt;/span&gt;
&lt;span class="py"&gt;multi_team&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;True&lt;/span&gt;
&lt;span class="py"&gt;simple_auth_manager_users&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"bob:admin:team1|team2,peter:viewer:team1,alice:op:team2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, bob is an admin with access to both team1 and team2, peter is a viewer restricted to team1, and alice is an op restricted to team2.&lt;/p&gt;

&lt;p&gt;A couple of details worth flagging:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Resources that aren't explicitly assigned to a team are treated as global and remain accessible to everyone, including team-restricted users.&lt;/li&gt;
&lt;li&gt;Admins bypass team restrictions entirely. You can still assign an admin to a team, but it has no practical effect since admins already have access to everything.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Disabling Authentication Entirely
&lt;/h2&gt;

&lt;p&gt;For fast local development or throwaway testing environments, the Simple Auth Manager offers an option to skip authentication altogether. This lets anyone who accesses the Airflow UI automatically log in as an admin with full permissions.&lt;/p&gt;

&lt;p&gt;You enable it like this:&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="nn"&gt;[core]&lt;/span&gt;
&lt;span class="py"&gt;simple_auth_manager_all_admins&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"True"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is convenient when you're spinning up a local instance just to test a DAG, but it should never be used anywhere reachable outside your own machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating a JWT Token for the API
&lt;/h2&gt;

&lt;p&gt;Airflow 3's &lt;a href="https://airflow.apache.org/docs/apache-airflow/stable/stable-rest-api-ref.html" rel="noopener noreferrer"&gt;public REST API&lt;/a&gt; is JWT-based, and the Simple Auth Manager provides the endpoint that issues those tokens. To generate one, send a POST request with your username and password:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;ENDPOINT_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8080"&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; &lt;span class="s1"&gt;'POST'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ENDPOINT_URL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/auth/token"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "username": "&amp;lt;username&amp;gt;",
    "password": "&amp;lt;password&amp;gt;"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can then include the returned token in subsequent API requests. If &lt;code&gt;simple_auth_manager_all_admins&lt;/code&gt; is enabled, you can generate a token without providing any credentials at all. For more detail, see the &lt;a href="https://airflow.apache.org/docs/apache-airflow/stable/core-concepts/auth-manager/simple/token.html" rel="noopener noreferrer"&gt;Simple Auth Manager token guide&lt;/a&gt; and the &lt;a href="https://airflow.apache.org/docs/apache-airflow/stable/core-concepts/auth-manager/simple/sam-token-api-ref.html" rel="noopener noreferrer"&gt;token API reference&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Things to Keep in Mind
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;It's not built for production.&lt;/strong&gt; This is the single most important thing to understand. The Simple Auth Manager exists to give Airflow 3 a working, dependency-light default so the core project could drop its hard reliance on FAB. If you're deploying to production, you should either configure a different auth manager or make sure access to your Airflow environment is locked down through other means (network isolation, a reverse proxy with its own auth layer, VPN access, and so on).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No database, no dynamic role editing.&lt;/strong&gt; Everything lives in your configuration file. That makes it simple to version-control and reason about, but it also means you can't add or edit roles without changing the auth manager implementation itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Passwords are ephemeral by default.&lt;/strong&gt; Since Airflow generates them fresh unless you intervene, plan for how you'll retrieve or fix credentials, particularly in containerized or ephemeral environments like Docker Compose or Kubernetes, where logs might not be easy to inspect after the fact.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-team isolation isn't full multi-tenancy.&lt;/strong&gt; It's resource-level filtering based on team assignment, not a hard security boundary. Global (unassigned) resources are visible to everyone, so don't rely on it as your only access control layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Switching auth managers later is a heavy lift.&lt;/strong&gt; If you start with the Simple Auth Manager and later move to a production-grade option, all users, roles, and permissions need to be recreated in the new system. It's worth deciding early if the Simple Auth Manager is only a placeholder for you or a long-term fit.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Is Simple Auth Manager safe to use in production?&lt;/strong&gt;&lt;br&gt;
No. It's explicitly intended for development and testing only. For production, use a different auth manager and control access through additional layers like network isolation or a reverse proxy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where do I find my auto-generated password?&lt;/strong&gt;&lt;br&gt;
Check the webserver logs on startup, or look in the file set by &lt;code&gt;core.simple_auth_manager_passwords_file&lt;/code&gt; (default: &lt;code&gt;$AIRFLOW_HOME/simple_auth_manager_passwords.json.generated&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I create custom roles in Simple Auth Manager?&lt;/strong&gt;&lt;br&gt;
No. Only four fixed roles exist: Viewer, User, Op, and Admin. Custom roles require a different auth manager.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I disable login entirely for local testing?&lt;/strong&gt;&lt;br&gt;
Set &lt;code&gt;simple_auth_manager_all_admins = "True"&lt;/code&gt; under &lt;code&gt;[core]&lt;/code&gt; in your Airflow configuration. Everyone who accesses the UI will be logged in as an admin automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;The Simple Auth Manager is Apache Airflow's answer to needing a lightweight, dependency-free authentication system out of the box for Airflow 3. It's easy to configure, requires no database, and gets you up and running in minutes with config-defined users and four fixed roles. But its simplicity is also its boundary: no custom roles, no dynamic user management, and an explicit warning against production use. If you're evaluating Airflow 3 for a real deployment, treat the Simple Auth Manager as your development sandbox and plan your production authentication strategy separately.&lt;/p&gt;

&lt;p&gt;For more on Airflow's authentication options, see the official &lt;a href="https://airflow.apache.org/docs/apache-airflow/stable/core-concepts/auth-manager/index.html" rel="noopener noreferrer"&gt;Auth Manager documentation&lt;/a&gt; and &lt;a href="https://airflow.apache.org/docs/apache-airflow/stable/security/index.html" rel="noopener noreferrer"&gt;Airflow Security guide&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>airflow</category>
      <category>kafka</category>
      <category>linux</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>What Is a Proxy Server? Types, Setup, and How It Differs From a VPN</title>
      <dc:creator>Sospeter Mong'are</dc:creator>
      <pubDate>Wed, 15 Jul 2026 05:59:40 +0000</pubDate>
      <link>https://dev.to/msnmongare/proxies-explained-types-setup-and-how-they-differ-from-a-vpn-3k91</link>
      <guid>https://dev.to/msnmongare/proxies-explained-types-setup-and-how-they-differ-from-a-vpn-3k91</guid>
      <description>&lt;p&gt;If you've ever looked into web scraping, bypassing geo-restricted content, or just browsing more privately, you've probably run into the term "proxy." But what exactly is it, and how does it stack up against a VPN? This guide breaks down everything you need to know before buying or setting one up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is a Proxy?
&lt;/h2&gt;

&lt;p&gt;A proxy server acts as a middleman between your computer and the internet. Instead of connecting directly to a website, your traffic first passes through the proxy server, which forwards your request and relays the response back to you. In the process, it can mask your real IP address, filter content, cache data, or help you get around geographic restrictions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Types of Proxies
&lt;/h2&gt;

&lt;p&gt;Proxies aren't one-size-fits-all. They vary by protocol, anonymity level, IP source, and intended use case.&lt;/p&gt;

&lt;h3&gt;
  
  
  By Protocol
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HTTP/HTTPS proxy&lt;/strong&gt;: Handles web traffic only, making it a good fit for browsing. It's simple and fast, but limited strictly to web protocols.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SOCKS proxy (SOCKS4/SOCKS5)&lt;/strong&gt;: Handles any type of traffic, including web browsing, torrents, email, and gaming. More flexible than HTTP proxies, though slightly slower since it lacks built-in caching.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  By Anonymity Level
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transparent proxy&lt;/strong&gt;: Doesn't hide your IP address; the destination server can see you're using a proxy and knows your real IP. Commonly used by companies and schools for content filtering.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anonymous proxy&lt;/strong&gt;: Hides your IP address but still identifies itself as a proxy to the destination server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Elite/high-anonymity proxy&lt;/strong&gt;: Hides your IP address and gives no indication that a proxy is being used at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  By Source or Ownership
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Datacenter proxy&lt;/strong&gt;: Uses IP addresses from cloud or hosting providers. Fast and inexpensive, but easier for websites to detect and block.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Residential proxy&lt;/strong&gt;: Uses IP addresses assigned by ISPs to real households. Harder to detect and typically more expensive, often used for web scraping or ad verification.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile proxy&lt;/strong&gt;: Uses IP addresses from mobile carriers (4G/5G networks). Very difficult to block, and usually the most expensive option.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  By Use Case
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reverse proxy&lt;/strong&gt;: Sits in front of a server (rather than your device) to distribute load, cache content, or add a layer of security. Nginx and Cloudflare are common examples.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rotating proxy&lt;/strong&gt;: Automatically switches IP addresses at set intervals, which is especially useful for web scraping at scale.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to Connect a Proxy on Your Computer
&lt;/h2&gt;

&lt;p&gt;Setting up a proxy varies slightly depending on your operating system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On Windows:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to Settings → Network &amp;amp; Internet → Proxy&lt;/li&gt;
&lt;li&gt;Under "Manual proxy setup," toggle on "Use a proxy server"&lt;/li&gt;
&lt;li&gt;Enter the proxy's IP address and port number&lt;/li&gt;
&lt;li&gt;Save your settings&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;On macOS:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to System Settings → Network → select your connection → Details (or Advanced)&lt;/li&gt;
&lt;li&gt;Open the "Proxies" tab&lt;/li&gt;
&lt;li&gt;Check the relevant protocol (Web Proxy HTTP, Secure Web Proxy HTTPS, or SOCKS Proxy)&lt;/li&gt;
&lt;li&gt;Enter the server address and port&lt;/li&gt;
&lt;li&gt;Apply the changes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;At the browser level:&lt;/strong&gt; Firefox allows you to configure proxy settings independently under Settings → Network Settings, without affecting the rest of your operating system. Chrome, by contrast, defaults to your system-wide proxy settings, though extensions like FoxyProxy can manage rules on a per-browser basis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Via command line or apps:&lt;/strong&gt; Many applications, including curl, git, and torrent clients, accept proxy settings directly through config files or environment variables such as &lt;code&gt;http_proxy&lt;/code&gt; and &lt;code&gt;https_proxy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Whichever method you choose, you'll need the proxy's IP address, port number, and possibly a username and password, all of which are provided by whoever supplies the proxy, such as a paid service or your workplace.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Confirm Your Proxy Is Connected
&lt;/h2&gt;

&lt;p&gt;Once you've bought a proxy and entered your settings, it's worth double-checking that it's actually working before you rely on it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Check whether your IP address changed.&lt;/strong&gt; Before connecting, visit a site like whatismyipaddress.com or ipinfo.io and note your current IP. Then enable the proxy and reload the same site. If the IP shown is now different, and matches the proxy provider's location (a different city or country), it's working.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Check the proxy status in your OS or browser settings.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Windows:&lt;/strong&gt; Settings → Network &amp;amp; Internet → Proxy should show "In use" next to your manual proxy entry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;macOS:&lt;/strong&gt; System Settings → Network → your connection → Details → Proxies should show your configured address under the checked protocol.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Firefox:&lt;/strong&gt; Settings → Network Settings will show your saved proxy configuration; there's no separate "connected" indicator, but if pages load and the IP check above confirms it, it's active.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Use command-line tools.&lt;/strong&gt; If you set proxy environment variables (&lt;code&gt;http_proxy&lt;/code&gt;/&lt;code&gt;https_proxy&lt;/code&gt;), test with:&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;-x&lt;/span&gt; http://your-proxy-ip:port https://ipinfo.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it returns the proxy's IP instead of your own, the connection is live.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Watch for authentication prompts.&lt;/strong&gt; If your proxy requires a username and password, your browser or OS should prompt you for credentials the first time you try to browse. If you're never prompted and pages fail to load, the proxy likely isn't applied correctly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Check for DNS leaks.&lt;/strong&gt; Even with a proxy active, your DNS requests might still go through your ISP, which can reveal your real location indirectly. Sites like dnsleaktest.com can confirm whether your DNS queries are also routed through the proxy or leaking.&lt;/p&gt;

&lt;p&gt;A few signs your proxy is &lt;em&gt;not&lt;/em&gt; working: your IP address stays the same as before, pages time out or fail to load entirely (often a wrong port or protocol mismatch), or you see "proxy refused connection" errors (usually wrong credentials or an IP not whitelisted by the provider).&lt;/p&gt;

&lt;p&gt;The quickest gut-check: change your settings, then check your IP on a site like ipinfo.io. If it shows the proxy's location instead of yours, you're connected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proxy vs. VPN: What's the Difference?
&lt;/h2&gt;

&lt;p&gt;While proxies and VPNs both reroute your internet traffic, they serve different purposes and offer very different levels of protection.&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;Proxy&lt;/th&gt;
&lt;th&gt;VPN&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Encryption&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Usually none&lt;/td&gt;
&lt;td&gt;Encrypts all traffic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scope&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Often app/browser-specific&lt;/td&gt;
&lt;td&gt;Covers the entire device&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Speed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Generally faster (no encryption overhead)&lt;/td&gt;
&lt;td&gt;Slightly slower due to encryption&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Security&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low, traffic can be intercepted&lt;/td&gt;
&lt;td&gt;High, encrypted tunnel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Use case&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bypassing geo-blocks, scraping, simple IP masking&lt;/td&gt;
&lt;td&gt;Privacy and security, especially on public Wi-Fi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DNS leaks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Common, since only some traffic is routed&lt;/td&gt;
&lt;td&gt;Typically routes DNS through the encrypted tunnel too&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;A proxy simply reroutes your traffic and hides your IP address for specific apps or browsers, but it doesn't encrypt anything. That means anyone snooping on the same network, like public Wi-Fi, could potentially still see your data. A VPN, on the other hand, encrypts everything at the operating-system level, protecting all of your device's traffic rather than just what passes through your browser.&lt;/p&gt;

&lt;p&gt;If your goal is something like web scraping or accessing region-locked content, a proxy is usually sufficient and more affordable. But if privacy and security are your priority, especially on public networks, a VPN is the better choice.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>linux</category>
      <category>proxy</category>
      <category>vpn</category>
    </item>
  </channel>
</rss>
