<?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: Vultr</title>
    <description>The latest articles on DEV Community by Vultr (vultr).</description>
    <link>https://dev.to/vultr</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%2Forganization%2Fprofile_image%2F13147%2Fb80ac1b8-cd21-44b9-9bf8-38224e413737.png</url>
      <title>DEV Community: Vultr</title>
      <link>https://dev.to/vultr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vultr"/>
    <language>en</language>
    <item>
      <title>Configuring Smart Routing in NVIDIA Dynamo</title>
      <dc:creator>Sanskriti Harmukh</dc:creator>
      <pubDate>Wed, 14 Oct 2026 21:07:15 +0000</pubDate>
      <link>https://dev.to/vultr/configuring-smart-routing-in-nvidia-dynamo-e25</link>
      <guid>https://dev.to/vultr/configuring-smart-routing-in-nvidia-dynamo-e25</guid>
      <description>&lt;p&gt;&lt;a href="https://docs.nvidia.com/dynamo/getting-started/quickstart" rel="noopener noreferrer"&gt;NVIDIA Dynamo&lt;/a&gt; is an open-source, high-throughput, low-latency inference framework designed to deploy large-scale generative AI and reasoning models across multi-node, multi-GPU environments. Its KV Router intelligently directs inference requests by evaluating computational costs across workers, considering both prefill and decode phases. By maximizing KV cache reuse and minimizing redundant computation, smart routing reduces time-to-first-token (TTFT) by up to 40% and increases overall throughput by 25-30% compared to round-robin load balancing. This guide covers infrastructure setup, router enablement for both aggregated and disaggregated serving patterns, parameter tuning, and monitoring for production deployments. By the end, you'll have KV-aware smart routing configured and verified across both serving patterns, with the parameters tuned for your workload.&lt;/p&gt;




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

&lt;p&gt;Before you begin, ensure you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Have access to a GPU-enabled server with NVIDIA GPUs installed and the NVIDIA Container Toolkit configured, using a non-root user with sudo privileges.

&lt;ul&gt;
&lt;li&gt;2 GPUs for aggregated serving with smart routing.&lt;/li&gt;
&lt;li&gt;4 GPUs for disaggregated serving with smart routing.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Install &lt;a href="https://docs.docker.com/engine/install/" rel="noopener noreferrer"&gt;Docker Engine&lt;/a&gt; and &lt;a href="https://docs.docker.com/compose/install/" rel="noopener noreferrer"&gt;Docker Compose&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Create a &lt;a href="https://huggingface.co/join" rel="noopener noreferrer"&gt;Hugging Face account&lt;/a&gt; and generate an &lt;a href="https://huggingface.co/settings/tokens" rel="noopener noreferrer"&gt;access token&lt;/a&gt; for gated models like Llama.&lt;/li&gt;
&lt;li&gt;Understand basic LLM inference concepts (prefill vs decode phases, KV cache).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Understanding KV-Aware Routing
&lt;/h2&gt;

&lt;p&gt;Traditional load balancing strategies like round-robin or random routing distribute requests evenly across workers without considering their cached state. KV-aware routing optimizes performance by directing requests to workers that already have relevant KV cache blocks, reducing redundant computation during the prefill phase.&lt;/p&gt;

&lt;h3&gt;
  
  
  How KV Cache Routing Works
&lt;/h3&gt;

&lt;p&gt;The KV Router makes routing decisions based on a cost function that balances two factors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Prefill Cost&lt;/strong&gt; — the router calculates prefill cost by determining how many tokens need processing from scratch. When a worker has cached KV blocks matching the request's input prefix, those blocks can be reused, reducing prefill computation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Prefill blocks = (Total input tokens - Cached tokens) / Block size
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Decode Cost&lt;/strong&gt; — the router tracks active blocks on each worker to estimate decode capacity. Workers with fewer active sequences have more capacity for new requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Combined Cost Formula:&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;cost = overlap_score_weight × prefill_blocks + decode_blocks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;overlap_score_weight&lt;/code&gt; balances TTFT optimization (higher values) vs load distribution (lower values).&lt;/li&gt;
&lt;li&gt;Lower cost indicates a better routing choice.&lt;/li&gt;
&lt;li&gt;Default &lt;code&gt;overlap_score_weight&lt;/code&gt; is 1.0.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Worker Selection Process
&lt;/h3&gt;

&lt;p&gt;The router evaluates all available workers and selects the one with the lowest cost. Consider this example with 3 workers and &lt;code&gt;overlap_score_weight = 1.0&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F06xpcw9923a1b14ozd79.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F06xpcw9923a1b14ozd79.png" alt="KV Cache Router Worker Selection Process" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Worker&lt;/th&gt;
&lt;th&gt;Cached Blocks&lt;/th&gt;
&lt;th&gt;Prefill Blocks&lt;/th&gt;
&lt;th&gt;Decode Blocks&lt;/th&gt;
&lt;th&gt;Total Cost&lt;/th&gt;
&lt;th&gt;Selected&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Worker 1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;18 (1.0 × 8 + 10)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worker 2&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;10&lt;/strong&gt; (1.0 × 5 + 5)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worker 3&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;11 (1.0 × 2 + 9)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Worker 2 provides the best balance of cache reuse and available capacity, minimizing both prefill computation and decode load.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance Benefits
&lt;/h3&gt;

&lt;p&gt;KV-aware routing provides measurable improvements over basic load balancing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TTFT Reduction&lt;/strong&gt;: 30-40% lower time-to-first-token by maximizing cache hits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Throughput Increase&lt;/strong&gt;: 25-30% higher tokens/second by reducing redundant prefill computation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better GPU Utilization&lt;/strong&gt;: More balanced load distribution across workers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache Efficiency&lt;/strong&gt;: Higher cache hit rates through intelligent request placement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These benefits are most significant for workloads with long input prompts (&amp;gt;2000 tokens) where prefill cost dominates, similar or repetitive input prefixes across requests, and multiple inference workers with independent KV caches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Clone the Dynamo Repository
&lt;/h2&gt;

&lt;p&gt;NVIDIA Dynamo provides deployment scripts, launch utilities, and runtime modules for inference workloads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Clone the repository:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git clone https://github.com/ai-dynamo/dynamo.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Navigate to the repository directory:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;dynamo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Switch to the latest stable release:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git checkout release/0.9.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Visit the &lt;a href="https://github.com/ai-dynamo/dynamo/releases/" rel="noopener noreferrer"&gt;Dynamo releases page&lt;/a&gt; to find the latest stable version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify CUDA Version and Pull the Container Image
&lt;/h2&gt;

&lt;p&gt;The vLLM container requires a CUDA version match between the host driver and container runtime to prevent GPU kernel incompatibilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Check the installed CUDA version:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nvidia-smi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output displays the CUDA version in the top-right corner of the table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 580.95.05              Driver Version: 580.95.05      CUDA Version: 13.0     |
+-----------------------------------------+------------------------+----------------------+
....
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Pull the vLLM container image from NGC, matching the image tag to your system's CUDA version:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For CUDA 13.x:&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker pull nvcr.io/nvidia/ai-dynamo/vllm-runtime:0.9.0-cuda13
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For CUDA 12.x:&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker pull nvcr.io/nvidia/ai-dynamo/vllm-runtime:0.9.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Visit the &lt;a href="https://catalog.ngc.nvidia.com/orgs/nvidia/teams/ai-dynamo/collections/ai-dynamo/artifacts" rel="noopener noreferrer"&gt;NVIDIA NGC Catalog&lt;/a&gt; to view all available image tags and CUDA versions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;3. (Optional) Build the container from source instead of pulling the pre-built image:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./container/build.sh &lt;span class="nt"&gt;--framework&lt;/span&gt; VLLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates an image named &lt;code&gt;dynamo:latest-vllm&lt;/code&gt;. If you use this locally built image, replace &lt;code&gt;nvcr.io/nvidia/ai-dynamo/vllm-runtime:0.9.0-cuda13&lt;/code&gt; with &lt;code&gt;dynamo:latest-vllm&lt;/code&gt; in all subsequent commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure Hugging Face Cache Permissions
&lt;/h2&gt;

&lt;p&gt;The container runs as UID 1000 and requires write access to the Hugging Face cache directory for model downloads. Incorrect permissions prevent the container from accessing cached model weights, causing worker initialization failures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Create the cache directory if it does not exist:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; container/.cache/huggingface
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Set ownership to the container user (UID 1000):&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; 1000:1000 container/.cache/huggingface
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Set appropriate permissions:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; 775 container/.cache/huggingface
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Start Infrastructure Services
&lt;/h2&gt;

&lt;p&gt;Dynamo's KV Router requires etcd for worker discovery and NATS for KV cache event propagation. The etcd service maintains a registry of active workers and their capabilities, while NATS handles real-time KV block events (creation and eviction) that let the router track cached state across workers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Start the infrastructure services using Docker Compose:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; deploy/docker-compose.yml up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Verify the services are running:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; deploy/docker-compose.yml ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output displays the running etcd and NATS containers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME                   IMAGE                      COMMAND                  SERVICE       CREATED          STATUS          PORTS
deploy-etcd-server-1   bitnamilegacy/etcd:3.6.1   "/opt/bitnami/script…"   etcd-server   18 seconds ago   Up 18 seconds   0.0.0.0:2379-2380-&amp;gt;2379-2380/tcp, [::]:2379-2380-&amp;gt;2379-2380/tcp
deploy-nats-server-1   nats:2.11.4                "/nats-server -c /et…"   nats-server   18 seconds ago   Up 18 seconds   0.0.0.0:4222-&amp;gt;4222/tcp, [::]:4222-&amp;gt;4222/tcp, 0.0.0.0:6222-&amp;gt;6222/tcp, [::]:6222-&amp;gt;6222/tcp, 0.0.0.0:8222-&amp;gt;8222/tcp, [::]:8222-&amp;gt;8222/tcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  KV Router Configuration Parameters
&lt;/h2&gt;

&lt;p&gt;The KV Router uses environment variables to control routing behavior, cache tracking, and worker selection logic. These parameters determine how the router balances Time To First Token (TTFT) optimization through cache reuse against Inter-Token Latency (ITL) through load distribution. All parameters can be set via environment variables or command-line arguments on the Dynamo frontend.&lt;/p&gt;

&lt;h3&gt;
  
  
  Core Router Parameters
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Recommended&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DYN_ROUTER_MODE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Enables KV cache-aware routing strategy&lt;/td&gt;
&lt;td&gt;&lt;code&gt;round_robin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Set to &lt;code&gt;kv&lt;/code&gt; to enable cache-aware routing. Other modes: &lt;code&gt;round_robin&lt;/code&gt;, &lt;code&gt;random&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DYN_ROUTER_TEMPERATURE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Controls routing randomness via softmax sampling&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;0.0&lt;/code&gt; = deterministic (always lowest cost). Higher values add randomness to prevent saturation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DYN_KV_OVERLAP_SCORE_WEIGHT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Weight for prefix cache overlaps in cost calculation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Formula: &lt;code&gt;cost = weight × prefill_blocks + decode_blocks&lt;/code&gt;. Higher values prioritize TTFT over ITL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DYN_KV_CACHE_BLOCK_SIZE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;KV cache block size in tokens (must match backend)&lt;/td&gt;
&lt;td&gt;Backend-specific&lt;/td&gt;
&lt;td&gt;&lt;code&gt;16&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Must match vLLM's block size. Used for prefill block calculation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DYN_KV_EVENTS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Enables real-time KV event tracking from workers&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;When disabled, router predicts cache state with TTL-based expiration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Worker Capacity Thresholds
&lt;/h3&gt;

&lt;p&gt;These parameters define when a worker is considered "busy" and should be excluded from routing decisions.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Recommended&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DYN_ACTIVE_DECODE_BLOCKS_THRESHOLD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Busy threshold based on KV cache block utilization&lt;/td&gt;
&lt;td&gt;Not set&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0.85&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Value between 0.0-1.0. Workers exceeding this percentage are excluded. Can update via &lt;code&gt;/busy_threshold&lt;/code&gt; API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DYN_ACTIVE_PREFILL_TOKENS_THRESHOLD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Busy threshold based on prefill token count&lt;/td&gt;
&lt;td&gt;Not set&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Absolute token count. Workers exceeding this are excluded. Uses OR logic with threshold fraction&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Infrastructure Configuration
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ETCD_ENDPOINTS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;etcd server URL for worker discovery&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Format: &lt;code&gt;http://localhost:2379&lt;/code&gt;. Multiple endpoints separated by comma&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NATS_SERVER&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;NATS server URL for KV event propagation&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Format: &lt;code&gt;nats://localhost:4222&lt;/code&gt;. Required for KV event streaming&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DYN_EVENT_PLANE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Event plane backend for KV cache events&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Set to &lt;code&gt;nats&lt;/code&gt; to enable NATS-based event publishing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PYTHONHASHSEED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deterministic hash seed for KV event IDs&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Set to &lt;code&gt;0&lt;/code&gt; for reproducible KV event ID generation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DYN_SYSTEM_PORT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Port for worker system status server&lt;/td&gt;
&lt;td&gt;System-dependent&lt;/td&gt;
&lt;td&gt;Used for health checks and metrics. Set per-worker to avoid conflicts (e.g., &lt;code&gt;8081&lt;/code&gt;, &lt;code&gt;8082&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Parameter Tuning Guidelines
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;For TTFT Optimization (Low Latency):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Increase &lt;code&gt;DYN_KV_OVERLAP_SCORE_WEIGHT&lt;/code&gt; to 1.5-2.0 (prioritize cache hits).&lt;/li&gt;
&lt;li&gt;Lower busy thresholds: &lt;code&gt;DYN_ACTIVE_DECODE_BLOCKS_THRESHOLD=0.70&lt;/code&gt;, &lt;code&gt;DYN_ACTIVE_PREFILL_TOKENS_THRESHOLD=500&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Keep &lt;code&gt;DYN_ROUTER_TEMPERATURE&lt;/code&gt; low (0.0-0.1) for deterministic selection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For Throughput Optimization (High Load):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lower &lt;code&gt;DYN_KV_OVERLAP_SCORE_WEIGHT&lt;/code&gt; to 0.5-1.0 (balance cache hits with load).&lt;/li&gt;
&lt;li&gt;Higher busy thresholds: &lt;code&gt;DYN_ACTIVE_DECODE_BLOCKS_THRESHOLD=0.95&lt;/code&gt;, &lt;code&gt;DYN_ACTIVE_PREFILL_TOKENS_THRESHOLD=2000&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Increase &lt;code&gt;DYN_ROUTER_TEMPERATURE&lt;/code&gt; to 0.3-0.5 for better load distribution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When KV Events Are Unavailable:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set &lt;code&gt;DYN_KV_EVENTS=false&lt;/code&gt; to use prediction-based routing.&lt;/li&gt;
&lt;li&gt;Router tracks state via routing decisions with TTL-based expiration (default 120s).&lt;/li&gt;
&lt;li&gt;Less accurate than real-time events but works without backend KV event support.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Set Up the Deployment Environment
&lt;/h2&gt;

&lt;p&gt;Both aggregated and disaggregated serving deployments use the same KV routing configuration. This section prepares the container environment and creates a shared configuration file for all deployment scripts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Export your Hugging Face token (replace &lt;code&gt;YOUR_HF_TOKEN&lt;/code&gt; with your actual token):&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;HF_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_HF_TOKEN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Run the vLLM container with GPU access and workspace mounting, using the image tag that matches your CUDA version:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./container/run.sh &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--framework&lt;/span&gt; VLLM &lt;span class="nt"&gt;--mount-workspace&lt;/span&gt; &lt;span class="nt"&gt;--image&lt;/span&gt; nvcr.io/nvidia/ai-dynamo/vllm-runtime:0.9.0-cuda13 &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;HF_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HF_TOKEN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This starts an interactive container session with GPU support and passes the Hugging Face token to the container.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Inside the container, create the common environment configuration file:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;' &amp;gt; ~/kv_routing.env
&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sh"&gt;Infrastructure endpoints
&lt;/span&gt;&lt;span class="go"&gt;export ETCD_ENDPOINTS=http://localhost:2379
export NATS_SERVER=nats://localhost:4222
export DYN_EVENT_PLANE=nats

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Set deterministic &lt;span class="nb"&gt;hash &lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;KV event IDs
&lt;span class="go"&gt;export PYTHONHASHSEED=0

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;KV Router Configuration
&lt;span class="go"&gt;export DYN_ROUTER_MODE=kv
export DYN_ROUTER_TEMPERATURE=0.1
export DYN_KV_OVERLAP_SCORE_WEIGHT=1.5
export DYN_KV_CACHE_BLOCK_SIZE=16
export DYN_KV_EVENTS=true

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Optional: Advanced routing configuration
&lt;span class="go"&gt;export DYN_ACTIVE_DECODE_BLOCKS_THRESHOLD=0.85
export DYN_ACTIVE_PREFILL_TOKENS_THRESHOLD=10000

echo "Environment configuration loaded:"
&lt;/span&gt;&lt;span class="gp"&gt;echo "  etcd: $&lt;/span&gt;ETCD_ENDPOINTS&lt;span class="s2"&gt;"
&lt;/span&gt;&lt;span class="gp"&gt;echo "  NATS: $&lt;/span&gt;&lt;span class="s2"&gt;NATS_SERVER"&lt;/span&gt;
&lt;span class="gp"&gt;echo "  Router Mode: $&lt;/span&gt;DYN_ROUTER_MODE&lt;span class="s2"&gt;"
&lt;/span&gt;&lt;span class="go"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file contains shared configuration variables for all deployment scripts, including etcd endpoints, NATS server address, and KV routing parameters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure KV Routing for Aggregated Serving
&lt;/h2&gt;

&lt;p&gt;Aggregated serving combines prefill and decode phases on a single worker. KV-aware routing optimizes which worker receives each request based on cached KV blocks, improving performance even when workers handle both phases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy Aggregated Serving with KV Routing
&lt;/h3&gt;

&lt;p&gt;The deployment script launches multiple vLLM workers with KV event publishing enabled and a frontend configured for KV-aware routing, handling process cleanup and providing routing configuration feedback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Inside the container, create the aggregated serving deployment script:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;' &amp;gt; ~/nemotron_agg_kv_routing.sh
&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="sh"&gt;!/bin/bash
&lt;/span&gt;&lt;span class="go"&gt;source ~/kv_routing.env

&lt;/span&gt;&lt;span class="gp"&gt;MODEL="$&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;1:-nvidia/Llama-3.1-Nemotron-Nano-4B-v1.1&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"
&lt;/span&gt;&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;pkill -f "dynamo.frontend";&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pkill &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"dynamo.vllm"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;sleep &lt;/span&gt;2
&lt;span class="go"&gt;set -e
trap 'kill 0' EXIT

python -m dynamo.frontend --router-reset-states &amp;amp;
sleep 10

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Worker 1 - GPU 0 &lt;span class="o"&gt;(&lt;/span&gt;ZMQ port 5557&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;DYN_SYSTEM_PORT=$&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;DYN_SYSTEM_PORT1:-8081&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;CUDA_VISIBLE_DEVICES=0 python3 -m dynamo.vllm \
&lt;/span&gt;&lt;span class="gp"&gt;    --model "$&lt;/span&gt;MODEL&lt;span class="s2"&gt;" &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;span class="go"&gt;    --trust-remote-code \
    --connector none \
    --enforce-eager \
    --enable-prefix-caching \
    --kv-events-config '{"enable_kv_cache_events":true,"endpoint":"tcp://*:5557"}' &amp;amp;

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Worker 2 - GPU 1 &lt;span class="o"&gt;(&lt;/span&gt;ZMQ port 5558&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;DYN_SYSTEM_PORT=$&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;DYN_SYSTEM_PORT2:-8082&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;CUDA_VISIBLE_DEVICES=1 python3 -m dynamo.vllm \
&lt;/span&gt;&lt;span class="gp"&gt;    --model "$&lt;/span&gt;MODEL&lt;span class="s2"&gt;" &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;span class="go"&gt;    --trust-remote-code \
    --connector none \
    --enforce-eager \
    --enable-prefix-caching \
    --kv-events-config '{"enable_kv_cache_events":true,"endpoint":"tcp://*:5558"}' &amp;amp;

sleep 30
&lt;/span&gt;&lt;span class="gp"&gt;echo "Aggregated serving ready at http://localhost:8000 (Model: $&lt;/span&gt;MODEL&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"
&lt;/span&gt;&lt;span class="go"&gt;wait
EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script configures aggregated serving with KV-aware routing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accepts the model name as a command-line argument (defaults to NVIDIA Nemotron Nano 4B).&lt;/li&gt;
&lt;li&gt;Sources the environment configuration created earlier (includes &lt;code&gt;DYN_EVENT_PLANE=nats&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Uses &lt;code&gt;--connector none&lt;/code&gt; for aggregated serving (prefill+decode on same worker).&lt;/li&gt;
&lt;li&gt;Uses &lt;code&gt;--enforce-eager&lt;/code&gt; for fast deployment without compilation.&lt;/li&gt;
&lt;li&gt;Enables prefix caching with &lt;code&gt;--enable-prefix-caching&lt;/code&gt; for KV cache reuse.&lt;/li&gt;
&lt;li&gt;Enables KV event publishing with ZMQ endpoints (ports 5557, 5558) on both workers.&lt;/li&gt;
&lt;li&gt;Configures system ports with &lt;code&gt;DYN_SYSTEM_PORT&lt;/code&gt; to avoid conflicts (8081, 8082).&lt;/li&gt;
&lt;li&gt;Uses &lt;code&gt;--trust-remote-code&lt;/code&gt; to support NVIDIA Nemotron models.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;KV Event Flow Architecture:&lt;/strong&gt; vLLM natively publishes KV cache events via ZMQ. Dynamo's bridge component automatically translates these events to NATS for router consumption:&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────┐
│ vLLM Worker 1       │
│ (ZMQ: tcp://*:5557) │───┐
└─────────────────────┘   │                                           ┌─────────────────────┐
                          │    ┌──────────────┐    ┌──────────────┐   │ KV Router           │
                          ├───&amp;gt;│ Dynamo Bridge├───&amp;gt;│ NATS Server  ├──&amp;gt;│ (Builds Radix Tree) │
                          │    └──────────────┘    └──────────────┘   │ Global Cache View   │
┌─────────────────────┐   │                                           └─────────────────────┘
│ vLLM Worker 2       │   │
│ (ZMQ: tcp://*:5558) │───┘
└─────────────────────┘
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;This architecture provides the router with a unified view of cache state across all workers while allowing vLLM to use its native ZMQ event publishing. You'll see ZMQ endpoints in worker logs, but events flow through the Dynamo bridge to NATS for router consumption.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;2. Make the script executable:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ~/nemotron_agg_kv_routing.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Run the aggregated serving script:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/nemotron_agg_kv_routing.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script starts the frontend with KV routing enabled and launches two vLLM workers on GPUs 0 and 1. The frontend connects to NATS and begins consuming KV events from all registered workers, building a prefix (radix) tree to track cached blocks across workers.&lt;/p&gt;

&lt;p&gt;To deploy a different model, pass the model name as an argument:&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/nemotron_agg_kv_routing.sh &lt;span class="s2"&gt;"nvidia/Llama-3_3-Nemotron-Super-49B-v1_5"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Verify KV Routing
&lt;/h3&gt;

&lt;p&gt;The router logs provide insights into worker selection and cost calculations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Open a new terminal session and send a test request to the frontend:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8000/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;  -H "Content-Type: application/json" \
  -d '{
    "model": "nvidia/Llama-3.1-Nemotron-Nano-4B-v1.1",
    "messages": [
      {"role": "system", "content": "You are a helpful AI assistant."},
      {"role": "user", "content": "Explain the concept of KV cache in LLM inference."}
    ],
    "max_tokens": 100,
    "temperature": 0.7
  }'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output displays the model's response in JSON format with streaming tokens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Check the frontend logs for routing decision details, which show cost calculations for each worker:&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;Formula for worker_id=7587893399646425890 dp_rank=0 with 0 cached blocks: 5.188 = 1.5 * prefill_blocks + decode_blocks = 1.5 * 2.125 + 2.000
Formula for worker_id=7587893399646425892 dp_rank=0 with 0 cached blocks: 5.188 = 1.5 * prefill_blocks + decode_blocks = 1.5 * 2.125 + 2.000
Selected worker: worker_id=7587893399646425892 dp_rank=0, logit: 5.188, cached blocks: 0, tree size: 0, total blocks: 75379
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The router calculates cost per worker with &lt;code&gt;cost = overlap_weight × prefill_blocks + decode_blocks&lt;/code&gt;. In this example, both workers have 0 cached blocks (cold start), equal prefill costs (2.125 blocks weighted by 1.5), and equal decode loads (2.000). With identical costs (5.188), the router selects a worker based on temperature sampling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Send a second request with a similar prompt prefix:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8000/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;  -H "Content-Type: application/json" \
  -d '{
    "model": "nvidia/Llama-3.1-Nemotron-Nano-4B-v1.1",
    "messages": [
      {"role": "system", "content": "You are a helpful AI assistant."},
      {"role": "user", "content": "Explain the concept of KV cache reuse in distributed LLM serving."}
    ],
    "max_tokens": 100,
    "temperature": 0.7
  }'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Check the routing decision for the second request in the logs.&lt;/strong&gt; If the router detects cached blocks from the previous request with a matching prompt prefix, the logs show a reduced prefill cost:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Formula for worker_id=7587893399646425890 dp_rank=0 with 0 cached blocks: 5.375 = 1.5 * prefill_blocks + decode_blocks = 1.5 * 2.250 + 2.000
Formula for worker_id=7587893399646425892 dp_rank=0 with 1 cached blocks: 3.875 = 1.5 * prefill_blocks + decode_blocks = 1.5 * 1.250 + 2.000
Selected worker: worker_id=7587893399646425892 dp_rank=0, logit: 3.875, cached blocks: 1, tree size: 8, total blocks: 75379
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The router prioritizes the worker with 1 cached block, reducing its cost from 5.375 to 3.875 — the cached block reduces prefill computation needed (1.250 blocks vs 2.250), demonstrating KV cache reuse resulting in faster TTFT for requests with overlapping prompt prefixes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Open a new terminal session on the host (outside the container) and monitor the KV event stream to verify event publishing:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--network&lt;/span&gt; container:deploy-nats-server-1 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;  natsio/nats-box nats stream info namespace-dynamo-component-backend-kv-events
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output displays the JetStream statistics including message count and consumer information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Information for Stream namespace-dynamo-component-backend-kv-events
......
                Subjects: namespace-dynamo-component-backend-kv-events.*
                Replicas: 1
                Storage: File
......
State:
......
                Messages: 8
                Bytes: 2.3 KiB
        Active Consumers: 1
......
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The message count increases as workers publish KV cache events — each message represents a KV cache state change, such as block creation or eviction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure KV Routing for Disaggregated Serving
&lt;/h2&gt;

&lt;p&gt;Disaggregated serving separates prefill and decode phases across different worker pools. The KV Router optimizes both prefill worker selection (based on cache hits) and decode worker assignment (based on load distribution), while coordinating KV cache transfer between phases via NIXL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy Disaggregated Serving with KV Routing
&lt;/h3&gt;

&lt;p&gt;The deployment script configures the frontend with increased overlap weight for prefill optimization, launches prefill workers with low batch sizes for TTFT optimization, and decode workers with high batch sizes for throughput maximization. The NIXL connector enables efficient KV cache transfer between prefill and decode workers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. If the aggregated serving script is still running from the previous section, stop it by pressing &lt;code&gt;Ctrl+C&lt;/code&gt; in the container terminal.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Inside the same container, create the disaggregated serving deployment script:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;' &amp;gt; ~/nemotron_disagg_kv_routing.sh
&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="sh"&gt;!/bin/bash
&lt;/span&gt;&lt;span class="go"&gt;source ~/kv_routing.env

&lt;/span&gt;&lt;span class="gp"&gt;MODEL="$&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;1:-nvidia/Llama-3.1-Nemotron-Nano-4B-v1.1&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"
&lt;/span&gt;&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;pkill -f "dynamo.frontend";&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pkill &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"dynamo.vllm"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;sleep &lt;/span&gt;2
&lt;span class="go"&gt;set -e
trap 'kill 0' EXIT

python -m dynamo.frontend --router-reset-states &amp;amp;
sleep 10

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Decode Worker 1 - GPU 0
&lt;span class="go"&gt;CUDA_VISIBLE_DEVICES=0 python3 -m dynamo.vllm \
&lt;/span&gt;&lt;span class="gp"&gt;    --model "$&lt;/span&gt;MODEL&lt;span class="s2"&gt;" &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;span class="go"&gt;    --connector nixl \
    --is-decode-worker \
    --max-model-len 2048 \
    --enable-prefix-caching \
    --trust-remote-code &amp;amp;

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Decode Worker 2 - GPU 1
&lt;span class="go"&gt;VLLM_NIXL_SIDE_CHANNEL_PORT=20097 \
CUDA_VISIBLE_DEVICES=1 python3 -m dynamo.vllm \
&lt;/span&gt;&lt;span class="gp"&gt;    --model "$&lt;/span&gt;MODEL&lt;span class="s2"&gt;" &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;span class="go"&gt;    --connector nixl \
    --is-decode-worker \
    --max-model-len 2048 \
    --enable-prefix-caching \
    --trust-remote-code &amp;amp;

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Prefill Worker 1 - GPU 2 &lt;span class="o"&gt;(&lt;/span&gt;ZMQ port 5559&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;CUDA_VISIBLE_DEVICES=2 python3 -m dynamo.vllm \
&lt;/span&gt;&lt;span class="gp"&gt;    --model "$&lt;/span&gt;MODEL&lt;span class="s2"&gt;" &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;span class="go"&gt;    --connector nixl \
    --is-prefill-worker \
    --max-model-len 2048 \
    --enable-prefix-caching \
    --trust-remote-code \
    --kv-events-config '{"enable_kv_cache_events":true,"endpoint":"tcp://*:5559"}' &amp;amp;

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Prefill Worker 2 - GPU 3 &lt;span class="o"&gt;(&lt;/span&gt;ZMQ port 5560&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;VLLM_NIXL_SIDE_CHANNEL_PORT=20099 \
CUDA_VISIBLE_DEVICES=3 python3 -m dynamo.vllm \
&lt;/span&gt;&lt;span class="gp"&gt;    --model "$&lt;/span&gt;MODEL&lt;span class="s2"&gt;" &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;span class="go"&gt;    --connector nixl \
    --is-prefill-worker \
    --max-model-len 2048 \
    --enable-prefix-caching \
    --trust-remote-code \
    --kv-events-config '{"enable_kv_cache_events":true,"endpoint":"tcp://*:5560"}' &amp;amp;

sleep 30
&lt;/span&gt;&lt;span class="gp"&gt;echo "Disaggregated serving ready at http://localhost:8000 (Model: $&lt;/span&gt;MODEL&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"
&lt;/span&gt;&lt;span class="go"&gt;echo "Architecture: 2 Decode Workers (GPU 0-1) + 2 Prefill Workers (GPU 2-3)"
wait
EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script configures disaggregated serving with KV-aware routing and NIXL for KV cache transfer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accepts the model name as a command-line argument (defaults to NVIDIA Nemotron Nano 4B).&lt;/li&gt;
&lt;li&gt;Sources the environment configuration created earlier (includes &lt;code&gt;DYN_EVENT_PLANE=nats&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Uses &lt;code&gt;--connector nixl&lt;/code&gt; to enable NIXL for KV cache transfer between prefill and decode workers.&lt;/li&gt;
&lt;li&gt;Sets unique NIXL ports via &lt;code&gt;VLLM_NIXL_SIDE_CHANNEL_PORT&lt;/code&gt; for workers 2 and 4 (20097, 20099).&lt;/li&gt;
&lt;li&gt;Enables prefix caching on all workers with &lt;code&gt;--enable-prefix-caching&lt;/code&gt; for KV cache reuse.&lt;/li&gt;
&lt;li&gt;Configures decode workers on GPUs 0-1 to receive KV cache from prefill workers via NIXL.&lt;/li&gt;
&lt;li&gt;Configures prefill workers on GPUs 2-3 with ZMQ endpoints (ports 5559, 5560) for KV event publishing.&lt;/li&gt;
&lt;li&gt;Uses &lt;code&gt;--trust-remote-code&lt;/code&gt; to support NVIDIA Nemotron models.&lt;/li&gt;
&lt;li&gt;Uses &lt;code&gt;--max-model-len 2048&lt;/code&gt; for efficient memory usage across all workers.&lt;/li&gt;
&lt;li&gt;Frontend automatically detects prefill and decode workers to enable disaggregated routing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Make the script executable:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ~/nemotron_disagg_kv_routing.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Run the disaggregated serving script:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/nemotron_disagg_kv_routing.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script starts the frontend, two decode workers on GPUs 0-1, and two prefill workers on GPUs 2-3. The frontend automatically detects prefill workers and creates an internal prefill router for cache-aware prefill worker selection, while the main router handles decode worker assignment based on load distribution.&lt;/p&gt;

&lt;p&gt;To deploy a different model, pass the model name as an argument:&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/nemotron_disagg_kv_routing.sh &lt;span class="s2"&gt;"nvidia/Llama-3_3-Nemotron-Super-49B-v1_5"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Verify Disaggregated KV Routing
&lt;/h3&gt;

&lt;p&gt;Disaggregated routing involves two routing decisions: prefill worker selection based on cache overlap, and decode worker assignment based on load and available capacity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Open a new terminal session and send a test request to verify the disaggregated flow:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8000/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;  -H "Content-Type: application/json" \
  -d '{
    "model": "nvidia/Llama-3.1-Nemotron-Nano-4B-v1.1",
    "messages": [
      {"role": "system", "content": "You are a helpful AI assistant specialized in distributed systems."},
      {"role": "user", "content": "Explain how KV cache transfer works in disaggregated LLM serving using NIXL."}
    ],
    "max_tokens": 150,
    "temperature": 0.7
  }'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Check the frontend logs for prefill routing decisions, which show prefill worker cost calculations with the higher overlap weight (1.5):&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;INFO dynamo_llm::kv_router::scheduler: Formula for worker_id=7587893399646425930 dp_rank=0 with 0 cached blocks: 4.125 = 1.5 * prefill_blocks + decode_blocks = 1.5 * 2.750 + 0.000
INFO dynamo_llm::kv_router::scheduler: Formula for worker_id=7587893399646425934 dp_rank=0 with 0 cached blocks: 4.125 = 1.5 * prefill_blocks + decode_blocks = 1.5 * 2.750 + 0.000
INFO dynamo_llm::kv_router::scheduler: Selected worker: worker_id=7587893399646425930 dp_rank=0, logit: 4.125, cached blocks: 0, tree size: 0, total blocks: 75379
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the first (cold-start) request, both prefill workers have 0 cached blocks, resulting in equal costs (4.125). The decode blocks component is 0.0 because prefill workers only handle the prefill phase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Check the decode routing decisions in the script output.&lt;/strong&gt; After prefill completion, the decode router assigns the request to a decode worker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO dynamo_llm::kv_router::scheduler: Formula for worker_id=7587893399646425936 dp_rank=0 with 0 cached blocks: 2.000 = 0.0 * prefill_blocks + decode_blocks = 0.0 * 2.750 + 2.000
INFO dynamo_llm::kv_router::scheduler: Selected worker: worker_id=7587893399646425936 dp_rank=0, logit: 2.000, cached blocks: 0, tree size: 0, total blocks: 75379
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The prefill blocks component is 0.0 in this formula because decode workers don't compute prefills — they receive KV cache from prefill workers via NIXL transfer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Monitor NIXL transfer success in the worker logs displayed by the script:&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;INFO nixl_connector._nixl_handshake: NIXL compatibility check passed (hash: cae818f9461cb277c305be4b8cd4aca3273e21a6ea98f7399d1a339560e1b3ea)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;NIXL successfully transfers KV cache blocks from prefill to decode workers using RDMA for low-latency, high-bandwidth data transfer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Send a second request with a similar prompt prefix to verify cache reuse:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8000/v1/chat/completions &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;  -H "Content-Type: application/json" \
  -d '{
    "model": "nvidia/Llama-3.1-Nemotron-Nano-4B-v1.1",
    "messages": [
      {"role": "system", "content": "You are a helpful AI assistant specialized in distributed systems."},
      {"role": "user", "content": "Explain how KV cache reuse works in disaggregated LLM serving."}
    ],
    "max_tokens": 150,
    "temperature": 0.7
  }'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6. Check the prefill routing logs for the second request, which show improved cache hits:&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;INFO dynamo_llm::kv_router::scheduler: Formula for worker_id=7587893399646425930 dp_rank=0 with 1 cached blocks: 2.250 = 1.5 * prefill_blocks + decode_blocks = 1.5 * 1.500 + 0.000
INFO dynamo_llm::kv_router::scheduler: Formula for worker_id=7587893399646425934 dp_rank=0 with 0 cached blocks: 3.750 = 1.5 * prefill_blocks + decode_blocks = 1.5 * 2.500 + 0.000
INFO dynamo_llm::kv_router::scheduler: Selected worker: worker_id=7587893399646425930 dp_rank=0, logit: 2.250, cached blocks: 1, tree size: 2, total blocks: 75379
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The prefill router now sees 1 cached block on the selected worker, reducing its cost from 3.750 to 2.250 — demonstrating KV-aware routing's ability to optimize TTFT by leveraging cached prompt prefixes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure KV Routing with SGLang and TensorRT-LLM
&lt;/h2&gt;

&lt;p&gt;The KV Router environment configuration is universal across all inference backends. The same environment variables used for vLLM deployments work identically with SGLang and TensorRT-LLM, requiring only backend-specific adjustments for block size and KV event publishing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Universal Environment Variables
&lt;/h3&gt;

&lt;p&gt;These environment variables work across all backends (vLLM, SGLang, TensorRT-LLM) without modification:&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;
&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Infrastructure endpoints
&lt;span class="go"&gt;export ETCD_ENDPOINTS=http://localhost:2379
export NATS_SERVER=nats://localhost:4222
export DYN_EVENT_PLANE=nats
export PYTHONHASHSEED=0

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Router configuration
&lt;span class="go"&gt;export DYN_ROUTER_MODE=kv
export DYN_ROUTER_TEMPERATURE=0.1
export DYN_KV_OVERLAP_SCORE_WEIGHT=1.5
export DYN_KV_EVENTS=true

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Load balancing thresholds
&lt;span class="go"&gt;export DYN_ACTIVE_DECODE_BLOCKS_THRESHOLD=0.85
export DYN_ACTIVE_PREFILL_TOKENS_THRESHOLD=10000
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Backend-Specific Configuration
&lt;/h3&gt;

&lt;p&gt;While the router environment variables are universal, two aspects require backend-specific configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Block Size Configuration&lt;/strong&gt; — &lt;code&gt;DYN_KV_CACHE_BLOCK_SIZE&lt;/code&gt; must match your backend's actual block size:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Backend&lt;/th&gt;
&lt;th&gt;Typical Block Size&lt;/th&gt;
&lt;th&gt;Configuration&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SGLang&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;&lt;code&gt;export DYN_KV_CACHE_BLOCK_SIZE=16&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TensorRT-LLM&lt;/td&gt;
&lt;td&gt;64 or 128 (varies by model)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;export DYN_KV_CACHE_BLOCK_SIZE=64&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vLLM&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;&lt;code&gt;export DYN_KV_CACHE_BLOCK_SIZE=16&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Mismatched block sizes between the router and workers cause incorrect cost calculations and routing decisions. Always verify your backend's block size configuration.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;KV Event Publishing Configuration&lt;/strong&gt; — each backend uses different command-line parameters to enable KV event publishing to the router:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Backend&lt;/th&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Example Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SGLang&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--kv-events-config&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nats://localhost:4222&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TensorRT-LLM&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--publish-events-and-metrics&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No value (flag only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vLLM&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--kv-events-config&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'{"enable_kv_cache_events":true,"endpoint":"tcp://*:5557"}'&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;SGLang publishes events using a NATS URL format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--kv-events-config nats://localhost:4222
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TensorRT-LLM uses a simple flag to enable event publishing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--publish-events-and-metrics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;vLLM uses a JSON configuration format with a ZMQ endpoint:&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="nt"&gt;--kv-events-config&lt;/span&gt; &lt;span class="s1"&gt;'{"enable_kv_cache_events":true,"endpoint":"tcp://*:5557"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tuning KV Router Parameters
&lt;/h2&gt;

&lt;p&gt;The KV Router provides several parameters to optimize routing behavior for different workload characteristics. Proper tuning balances cache reuse (TTFT optimization) with load distribution (ITL optimization).&lt;/p&gt;

&lt;h3&gt;
  
  
  Overlap Score Weight
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;--kv-overlap-score-weight&lt;/code&gt; parameter controls the router's preference for cache hits versus load balancing. Default value: &lt;code&gt;1.0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Increase (1.5-2.0) for prefill-heavy workloads — long input prompts (&amp;gt;2000 tokens), short output generation (&amp;lt;200 tokens), and where minimizing TTFT is critical:&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; dynamo.frontend &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;  --router-mode kv \
  --kv-overlap-score-weight 1.5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Decrease (0.5-0.8) for decode-heavy workloads — short input prompts (&amp;lt;500 tokens), long output generation (&amp;gt;1000 tokens), and where maximizing throughput is critical:&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; dynamo.frontend &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;  --router-mode kv \
  --kv-overlap-score-weight 0.7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set to &lt;code&gt;0.0&lt;/code&gt; to disable cache-aware routing and use pure load balancing:&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; dynamo.frontend &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;  --router-mode kv \
  --kv-overlap-score-weight 0.0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This mode ignores cached blocks and routes based solely on worker load — useful for workloads with no prefix overlap or for baseline performance comparisons.&lt;/p&gt;

&lt;h3&gt;
  
  
  Router Temperature
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;--router-temperature&lt;/code&gt; parameter introduces randomness in worker selection to prevent saturation and improve load distribution. Default value: &lt;code&gt;0.0&lt;/code&gt; (deterministic).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Temperature = 0.0&lt;/strong&gt; (default): Always select the lowest-cost worker. Best for maximizing cache reuse in stable workloads, with the risk of oversaturating workers with the best cache state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Temperature = 0.2-0.5&lt;/strong&gt;: Add slight randomness while still favoring low-cost workers. Best for production workloads with variable request patterns, giving better load distribution without sacrificing much cache efficiency:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; dynamo.frontend &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;  --router-mode kv \
  --router-temperature 0.3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Temperature = 1.0+&lt;/strong&gt;: More uniform distribution across workers. Best for debugging, testing, or when cache reuse is less important — approaches random selection at very high temperatures.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  KV Events Configuration
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;--no-kv-events&lt;/code&gt; flag disables real-time KV event tracking and switches to prediction-based routing. Default: KV events enabled.&lt;/p&gt;

&lt;p&gt;When enabled, the router predicts cache state based on its own routing decisions instead of receiving events from workers:&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; dynamo.frontend &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;  --router-mode kv \
  --no-kv-events \
  --router-ttl 120 \
  --router-max-tree-size 1048576 \
  --router-prune-target-ratio 0.8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additional parameters for prediction mode:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--router-ttl 120&lt;/code&gt;: Expire cached blocks after 120 seconds (default: 120).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--router-max-tree-size 1048576&lt;/code&gt;: Maximum blocks before pruning (default: 2^20).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--router-prune-target-ratio 0.8&lt;/code&gt;: Prune to 80% of max size when threshold exceeded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use prediction mode when your backend doesn't support KV event publishing, you want lower infrastructure overhead (no NATS dependency), or your deployment is simple enough that prediction accuracy is acceptable.&lt;/p&gt;

&lt;p&gt;When using &lt;code&gt;--no-kv-events&lt;/code&gt;, configure backends to disable event publishing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;vLLM&lt;/strong&gt;: Use &lt;code&gt;--kv-events-config '{"enable_kv_cache_events": false}'&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SGLang&lt;/strong&gt;: Do not use &lt;code&gt;--kv-events-config&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TRT-LLM&lt;/strong&gt;: Do not use &lt;code&gt;--publish-events-and-metrics&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Active Block Busy Detection
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;--active-decode-blocks-threshold&lt;/code&gt; parameter marks workers as busy when their KV cache utilization exceeds a threshold, preventing overload. Default: disabled (no threshold).&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; dynamo.frontend &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;  --router-mode kv \
  --active-decode-blocks-threshold 0.85 \
  --active-prefill-tokens-threshold 10000
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--active-decode-blocks-threshold 0.85&lt;/code&gt;: Mark worker busy at 85% KV cache utilization.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--active-prefill-tokens-threshold 10000&lt;/code&gt;: Mark worker busy when prefill tokens exceed 10,000.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Busy workers are excluded from routing decisions until their load drops below the threshold, preventing request queueing and maintaining consistent latency. Thresholds can be updated at runtime without restarting the frontend:&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8000/busy_threshold &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;  -H "Content-Type: application/json" \
  -d '{
    "model": "nvidia/Llama-3.1-Nemotron-Nano-4B-v1.1",
    "active_decode_blocks_threshold": 0.90,
    "active_prefill_tokens_threshold": 12000
  }'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response confirms the updated thresholds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nvidia/Llama-3.1-Nemotron-Nano-4B-v1.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"active_decode_blocks_threshold"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"active_prefill_tokens_threshold"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Router Replica Synchronization
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;--router-replica-sync&lt;/code&gt; flag enables active block state synchronization between multiple router replicas for consistent load balancing. Default: disabled.&lt;/p&gt;

&lt;p&gt;Enable it when launching multiple frontend replicas for fault tolerance and horizontal scaling:&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;
&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Router replica 1
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; dynamo.frontend &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;  --router-mode kv \
  --http-port 8000 \
  --router-replica-sync

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Router replica 2
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; dynamo.frontend &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;  --router-mode kv \
  --http-port 8001 \
  --router-replica-sync
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each replica maintains its own view of active blocks (ephemeral state); replicas exchange routing decisions via NATS core messaging, and cached block state (persistent) syncs automatically via JetStream. New replicas start with zero active blocks but converge quickly through sync, giving consistent routing decisions and better load distribution across replicas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitor KV Router Performance
&lt;/h2&gt;

&lt;p&gt;To monitor KV Router performance metrics including routing decisions, cache efficiency, and worker utilization, deploy an observability stack (Grafana and Prometheus) alongside your Dynamo deployment. Add the KV Router environment variables from this guide to your deployment configuration, then use Grafana dashboards to visualize KV cache routing performance metrics and Prometheus to track TTFT improvements, cache hit rates, and worker selection patterns.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Tune &lt;code&gt;DYN_KV_OVERLAP_SCORE_WEIGHT&lt;/code&gt; and &lt;code&gt;DYN_ROUTER_TEMPERATURE&lt;/code&gt; against your real traffic patterns rather than the defaults&lt;/li&gt;
&lt;li&gt;Stand up Grafana and Prometheus dashboards to track TTFT, cache hit rate, and worker load over time&lt;/li&gt;
&lt;li&gt;Test SGLang or TensorRT-LLM backends with the same router configuration to compare cache efficiency&lt;/li&gt;
&lt;li&gt;Enable &lt;code&gt;--router-replica-sync&lt;/code&gt; before scaling to multiple frontend replicas in production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the full guide with additional tips, visit the original article on &lt;strong&gt;&lt;a href="https://docs.vultr.com/how-to-configure-smart-routing-in-nvidia-dynamo" rel="noopener noreferrer"&gt;Vultr Docs&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>nvidia</category>
      <category>gpu</category>
      <category>inference</category>
    </item>
    <item>
      <title>Automating Bash Scripts on Linux Using Cron and Systemd</title>
      <dc:creator>Sanskriti Harmukh</dc:creator>
      <pubDate>Wed, 14 Oct 2026 21:06:31 +0000</pubDate>
      <link>https://dev.to/vultr/automating-bash-scripts-on-linux-using-cron-and-systemd-3nie</link>
      <guid>https://dev.to/vultr/automating-bash-scripts-on-linux-using-cron-and-systemd-3nie</guid>
      <description>&lt;p&gt;Automating Bash scripts on Linux is essential for system administration tasks, such as installing applications and monitoring system logs. Cron is a job scheduler that runs tasks at specific intervals, while systemd is a service manager that handles applications through service profiles. This guide walks through writing two Bash scripts — one for log monitoring and one for backups — then automating them with a cron job and running one as a persistent systemd service. By the end, you'll have both a scheduled cron job and a managed systemd service running your own Bash scripts automatically.&lt;/p&gt;




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

&lt;p&gt;Before you begin:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Have access to a Ubuntu server as your management workstation.&lt;/li&gt;
&lt;li&gt;Access the server using SSH as a non-root sudo user.&lt;/li&gt;
&lt;li&gt;Install a LAMP stack (Apache, MySQL, PHP).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Create Bash Scripts
&lt;/h2&gt;

&lt;p&gt;Bash scripts support multiple commands and functions that define how tasks run in a system. Follow the steps below to create two Bash scripts: one to back up critical web application files, and one to monitor system logs for errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Switch to your user's home directory:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Create a new &lt;code&gt;log-monitor.sh&lt;/code&gt; script:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nano log-monitor.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Add the following contents to the &lt;code&gt;log-monitor.sh&lt;/code&gt; file:&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;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;LOG_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/var/log/syslog"&lt;/span&gt; 
&lt;span class="nv"&gt;ERROR_LOG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/opt/data/error_log.txt"&lt;/span&gt;
&lt;span class="nv"&gt;WARNING_LOG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/opt/data/warning_log.txt"&lt;/span&gt;

&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read &lt;/span&gt;line&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    if &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$line&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$line&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ERROR_LOG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;elif &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$line&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s2"&gt;"warning"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$line&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WARNING_LOG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;fi
done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save and close the file. This script creates &lt;code&gt;error_log.txt&lt;/code&gt; and &lt;code&gt;warning_log.txt&lt;/code&gt; files in the &lt;code&gt;/opt/data&lt;/code&gt; directory, filtering results from &lt;code&gt;/var/log/syslog&lt;/code&gt; using a &lt;code&gt;while&lt;/code&gt; loop. It writes each new line to the respective error or warning file as it appears.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Create another script named &lt;code&gt;backup.sh&lt;/code&gt;:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nano backup.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Add the following contents to the file:&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;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;DB_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;
&lt;span class="nv"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"new-password"&lt;/span&gt;
&lt;span class="nv"&gt;DB_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"mysql"&lt;/span&gt;

&lt;span class="nv"&gt;DB_BACKUP_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"mysql_backup"&lt;/span&gt;
&lt;span class="nv"&gt;WEB_FILES_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"web_backup"&lt;/span&gt;

&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DB_BACKUP_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WEB_FILES_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&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; +&lt;span class="s2"&gt;"%Y%m%d_%H%M%S"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

mysqldump &lt;span class="nt"&gt;-u&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DB_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DB_PASSWORD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DB_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&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;$DB_BACKUP_DIR&lt;/span&gt;&lt;span class="s2"&gt;/db_backup_&lt;/span&gt;&lt;span class="nv"&gt;$DATE&lt;/span&gt;&lt;span class="s2"&gt;.sql"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"MySQL backup completed: &lt;/span&gt;&lt;span class="nv"&gt;$DB_BACKUP_DIR&lt;/span&gt;&lt;span class="s2"&gt;/db_backup_&lt;/span&gt;&lt;span class="nv"&gt;$DATE&lt;/span&gt;&lt;span class="s2"&gt;.sql"&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;$WEB_FILES_DIR&lt;/span&gt;&lt;span class="s2"&gt;/web_backup_&lt;/span&gt;&lt;span class="nv"&gt;$DATE&lt;/span&gt;&lt;span class="s2"&gt;.tar.gz"&lt;/span&gt; /var/www/html
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Web application backup completed: &lt;/span&gt;&lt;span class="nv"&gt;$WEB_FILES_DIR&lt;/span&gt;&lt;span class="s2"&gt;/web_backup_&lt;/span&gt;&lt;span class="nv"&gt;$DATE&lt;/span&gt;&lt;span class="s2"&gt;.tar.gz"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save and close the file. This script connects to the MySQL database server using the &lt;code&gt;DB_...&lt;/code&gt; variables, then creates &lt;code&gt;mysql_backup&lt;/code&gt; and &lt;code&gt;web_backup&lt;/code&gt; directories to store the database and web application backups.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Move the Bash scripts to a system-wide directory like &lt;code&gt;/opt&lt;/code&gt;:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo cp &lt;/span&gt;backup.sh log-monitor.sh /opt/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;7. Create a new &lt;code&gt;data&lt;/code&gt; directory under the system-wide directory:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /opt/data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;8. Change the directory permissions to &lt;code&gt;777&lt;/code&gt; to allow all users to write files:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;777 /opt/data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Automate the Bash Script Using Cron
&lt;/h2&gt;

&lt;p&gt;Cron creates jobs you can use to schedule and automatically run a Bash script at specific intervals. Follow the steps below to automate the &lt;code&gt;backup.sh&lt;/code&gt; script to back up the MySQL database and web application files daily.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Open the crontab editor:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &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;Select your desired text editor when prompted — for example, select &lt;code&gt;1&lt;/code&gt; to choose the &lt;code&gt;nano&lt;/code&gt; text editor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Add the following directive at the end of the file:&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;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/backup.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save and close the file. This cron job runs &lt;code&gt;backup.sh&lt;/code&gt; daily at 2:00 A.M. In the job:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;0&lt;/code&gt;: Runs the script at the start of a minute. Set a value between &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;59&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;2&lt;/code&gt;: Runs the script at 2:00 A.M. Specify an hour between &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;23&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;*&lt;/code&gt;: Runs the script every day of the month. Specify a day between &lt;code&gt;1&lt;/code&gt; and &lt;code&gt;31&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;*&lt;/code&gt;: Runs the script every month. Specify a month between &lt;code&gt;1&lt;/code&gt; and &lt;code&gt;12&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;*&lt;/code&gt;: Runs the script daily. Specify a day of the week between &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;7&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. List all cron jobs and verify the new task is available:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;crontab &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&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;# &lt;/span&gt;
&lt;span class="c"&gt;# For more information see the manual pages of crontab(5) and cron(8)&lt;/span&gt;
&lt;span class="c"&gt;# &lt;/span&gt;
&lt;span class="c"&gt;# m h  dom mon dow   command&lt;/span&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; ~/backup.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Automate the Bash Script as a System Service Using Systemd
&lt;/h2&gt;

&lt;p&gt;Systemd starts system services automatically at boot and offers options for stopping and restarting them. Follow the steps below to set up the log-monitoring script as a system service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Create a new system service file:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/systemd/system/logmonitor.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Add the following contents to the &lt;code&gt;logmonitor.service&lt;/code&gt; file:&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="o"&gt;[&lt;/span&gt;Unit]
&lt;span class="nv"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; Automatic System Logs Monitoring and Filtering
&lt;span class="nv"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;network.target

&lt;span class="o"&gt;[&lt;/span&gt;Service]
&lt;span class="nv"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;bash /opt/log-monitor.sh
&lt;span class="nv"&gt;Restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;always

&lt;span class="o"&gt;[&lt;/span&gt;Install]
&lt;span class="nv"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;multi-user.target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save and close the file. This service configuration runs the &lt;code&gt;log-monitor.sh&lt;/code&gt; script from &lt;code&gt;/opt&lt;/code&gt; to monitor and filter log entries in &lt;code&gt;/var/log/syslog&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Reload systemd to apply the service file changes:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl daemon-reload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Enable the new system service to automatically start at boot:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;logmonitor.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Created symlink /etc/systemd/system/multi-user.target.wants/logmonitor.service → /etc/systemd/system/logmonitor.service.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Start the system service:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start logmonitor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6. View the system service status and verify it's running:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status logmonitor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;● logmonitor.service - Automatic System Logs Monitoring and Filtering
 Loaded: loaded (/etc/systemd/system/logmonitor.service; enabled; preset: enabled)
 Active: active (running) since Wed 2024-11-06 13:20:07 UTC; 11s ago
Main PID: 14293 (bash)
   Tasks: 3 (limit: 1062)
  Memory: 784.0K (peak: 1.5M)
     CPU: 40ms
  CGroup: /system.slice/logmonitor.service
          ├─14293 bash /opt/log-monitor.sh
          ├─14308 tail -F /var/log/syslog
          └─14309 bash /opt/log-monitor.sh

Nov 06 13:20:07 Linux-server systemd[1]: Started logmonitor.service - Automatic System Logs Monitoring and Filtering.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;7. Run the following commands to simulate new error and warning entries in &lt;code&gt;/var/log/syslog&lt;/code&gt;:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;logger &lt;span class="nt"&gt;-p&lt;/span&gt; user.err &lt;span class="s2"&gt;"This is a simulated error log message from MySQL"&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;logger &lt;span class="nt"&gt;-p&lt;/span&gt; user.err &lt;span class="s2"&gt;"This is a simulated warning log message from Apache"&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;logger &lt;span class="nt"&gt;-p&lt;/span&gt; user.err &lt;span class="s2"&gt;"This is a simulated error log message from PHP"&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;logger &lt;span class="nt"&gt;-p&lt;/span&gt; user.err &lt;span class="s2"&gt;"This is a simulated Warning log message from MySQL"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;8. List all files in &lt;code&gt;/opt/data&lt;/code&gt; and verify that &lt;code&gt;error_log.txt&lt;/code&gt; and &lt;code&gt;warning_log.txt&lt;/code&gt; are available:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; /opt/data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error_log.txt warning_log.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;9. View the &lt;code&gt;error_log.txt&lt;/code&gt; file and verify that only error entries are present:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;error.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2024-11-06T13:33:48.010096+00:00 Server linuxuser: This is a simulated error log message from MySQL
2024-11-06T13:33:48.010096+00:00 Server linuxuser: This is a simulated error log message from PHP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Add more granular filters (by service name or severity) to &lt;code&gt;log-monitor.sh&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Extend &lt;code&gt;backup.sh&lt;/code&gt; with retention logic to prune old backups automatically&lt;/li&gt;
&lt;li&gt;Ship backups to remote or off-site storage so they survive a full server loss&lt;/li&gt;
&lt;li&gt;Add alerting (email or webhook) when new entries land in &lt;code&gt;error_log.txt&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the full guide with additional tips, visit the original article on &lt;strong&gt;&lt;a href="https://docs.vultr.com/how-to-automate-bash-scripts-on-linux-using-cron-and-systemd" rel="noopener noreferrer"&gt;Vultr Docs&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>bash</category>
      <category>cron</category>
      <category>systemd</category>
    </item>
    <item>
      <title>Managing File and Directory Permissions in Linux</title>
      <dc:creator>Sanskriti Harmukh</dc:creator>
      <pubDate>Wed, 14 Oct 2026 21:05:56 +0000</pubDate>
      <link>https://dev.to/vultr/managing-file-and-directory-permissions-in-linux-33cp</link>
      <guid>https://dev.to/vultr/managing-file-and-directory-permissions-in-linux-33cp</guid>
      <description>&lt;p&gt;Linux permissions control how users access, modify, and execute files or directories on a system. Efficient management of user permissions improves system security and access control by preventing unauthorized access to sensitive data, files, and directories. This guide explains how to read and interpret Linux permissions, then use the &lt;code&gt;chmod&lt;/code&gt; and &lt;code&gt;chown&lt;/code&gt; commands to change them. By the end, you'll be able to view, calculate, and modify file and directory permissions — including special permissions like the sticky bit, SUID, and SGID — on your Linux workstation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Linux Permissions
&lt;/h2&gt;

&lt;p&gt;Linux permissions are categorized into read, write, and execute (RWX), which define user or group privileges on a file or directory:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read (&lt;code&gt;r&lt;/code&gt;)&lt;/strong&gt;: View the contents of a file or list the contents of a directory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write (&lt;code&gt;w&lt;/code&gt;)&lt;/strong&gt;: Modify, add, remove, or rename files in a directory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execute (&lt;code&gt;x&lt;/code&gt;)&lt;/strong&gt;: Run a file or access the files in a directory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Permissions can be assigned to the following user classes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Owner&lt;/strong&gt;: The user who owns the file or directory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Group&lt;/strong&gt;: The group associated with the file or directory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Others&lt;/strong&gt;: All other system users.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a user owns a file and also belongs to its group, the owner's permissions override the group's permissions. If the owner has read permission (&lt;code&gt;r&lt;/code&gt;) but no write or execute permissions, they cannot write or execute the file even if the group has full permissions (&lt;code&gt;rwx&lt;/code&gt;). You can add users to a group with the &lt;code&gt;usermod&lt;/code&gt; or &lt;code&gt;gpasswd&lt;/code&gt; command to grant them specific group permissions.&lt;/p&gt;

&lt;h3&gt;
  
  
  String Representation of Linux Permissions
&lt;/h3&gt;

&lt;p&gt;Files and directories use a 10-character format to represent permissions, visible by running a command such as &lt;code&gt;ls -l&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first character represents the file type:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-&lt;/code&gt;: Regular file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;d&lt;/code&gt;: Directory.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;l&lt;/code&gt;: Symbolic link.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;b&lt;/code&gt;: Block device or special file, such as disks and partitions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;c&lt;/code&gt;: Character device or special file, such as terminals and serial ports.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;p&lt;/code&gt;: Named pipe.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;s&lt;/code&gt;: Socket.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;D&lt;/code&gt;: Door.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The next nine characters represent owner, group, and others permissions in order (three characters each).&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;drwxrw-r-- example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;d&lt;/code&gt;: Specifies that &lt;code&gt;example.com&lt;/code&gt; is a directory.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rwx&lt;/code&gt;: The owner has read, write, and execute permissions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rw-&lt;/code&gt;: The group has read and write permissions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;r--&lt;/code&gt;: Other users have read-only permissions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to Calculate Linux Permissions
&lt;/h2&gt;

&lt;p&gt;Linux permissions use binary values to represent access levels:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Permission&lt;/th&gt;
&lt;th&gt;Binary&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;Read (r)&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write (w)&lt;/td&gt;
&lt;td&gt;010&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Execute (x)&lt;/td&gt;
&lt;td&gt;001&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;None (-)&lt;/td&gt;
&lt;td&gt;000&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read + Write&lt;/strong&gt;: &lt;code&gt;4 + 2 = 6&lt;/code&gt; (binary: &lt;code&gt;110&lt;/code&gt;, symbol: &lt;code&gt;rw-&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read + Execute&lt;/strong&gt;: &lt;code&gt;4 + 1 = 5&lt;/code&gt; (binary: &lt;code&gt;101&lt;/code&gt;, symbol: &lt;code&gt;r-x&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write + Execute&lt;/strong&gt;: &lt;code&gt;2 + 1 = 3&lt;/code&gt; (binary: &lt;code&gt;011&lt;/code&gt;, symbol: &lt;code&gt;-wx&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read + Write + Execute&lt;/strong&gt;: &lt;code&gt;4 + 2 + 1 = 7&lt;/code&gt; (binary: &lt;code&gt;111&lt;/code&gt;, symbol &lt;code&gt;rwx&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Common Linux Permissions
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;644&lt;/strong&gt; — owner can read and write; group and others can only read:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Permission Type&lt;/th&gt;
&lt;th&gt;Read (r)&lt;/th&gt;
&lt;th&gt;Write (w)&lt;/th&gt;
&lt;th&gt;Execute (x)&lt;/th&gt;
&lt;th&gt;None (-)&lt;/th&gt;
&lt;th&gt;Total Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Owner (u)&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Group (g)&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Others (o)&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;755&lt;/strong&gt; — owner has full permissions; group and others have read and execute:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Permission Type&lt;/th&gt;
&lt;th&gt;Read (r)&lt;/th&gt;
&lt;th&gt;Write (w)&lt;/th&gt;
&lt;th&gt;Execute (x)&lt;/th&gt;
&lt;th&gt;None (-)&lt;/th&gt;
&lt;th&gt;Total Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Owner (u)&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Group (g)&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Others (o)&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&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;&lt;strong&gt;777&lt;/strong&gt; — owner, group, and others all have full permissions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Permission Type&lt;/th&gt;
&lt;th&gt;Read (r)&lt;/th&gt;
&lt;th&gt;Write (w)&lt;/th&gt;
&lt;th&gt;Execute (x)&lt;/th&gt;
&lt;th&gt;None (-)&lt;/th&gt;
&lt;th&gt;Total Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Owner (u)&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Group (g)&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Others (o)&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;The root user can override all Linux user permissions on a file or directory. Using &lt;code&gt;sudo&lt;/code&gt; while reading or executing a file enables root superuser privileges, overriding any existing permissions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  View File and Directory Permissions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Create a new sample file and directory:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;file.txt &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; /var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. View the permissions on the file with &lt;code&gt;ls -l&lt;/code&gt;:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; file.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should be similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-rwxr-xr-x 1 user group 1024 Jan 1 12:00 file.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-&lt;/code&gt;: Shows that &lt;code&gt;file.txt&lt;/code&gt; is a regular file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rwx&lt;/code&gt;: The owner's permissions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;r-x&lt;/code&gt;: The group permissions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;r-x&lt;/code&gt;: Other users' permissions on the file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. View the directory permissions with &lt;code&gt;ls -ld&lt;/code&gt;:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-ld&lt;/span&gt; /var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should be similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;drwxr-xr-x 2 user group 4096 Jan 1 12:30 /var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;d&lt;/code&gt;: Shows that &lt;code&gt;/var/www/html&lt;/code&gt; is a directory.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rwx&lt;/code&gt;: The directory owner's permissions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;r-x&lt;/code&gt;: The group permissions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;r-x&lt;/code&gt;: Other users' permissions on the directory.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Change File and Directory Permissions
&lt;/h2&gt;

&lt;p&gt;You can change permissions using the &lt;code&gt;chmod&lt;/code&gt; command in two modes: numeric and symbolic.&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;permissions] &lt;span class="o"&gt;[&lt;/span&gt;file or directory]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Change Permissions Using Numeric Mode
&lt;/h3&gt;

&lt;p&gt;Assign permissions using three digits representing the owner, group, and others. Each digit is the sum of read (&lt;code&gt;4&lt;/code&gt;), write (&lt;code&gt;2&lt;/code&gt;), and execute (&lt;code&gt;1&lt;/code&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read (&lt;code&gt;r&lt;/code&gt;): &lt;code&gt;4&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Write (&lt;code&gt;w&lt;/code&gt;): &lt;code&gt;2&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Execute (&lt;code&gt;x&lt;/code&gt;): &lt;code&gt;1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;No permissions (&lt;code&gt;-&lt;/code&gt;): &lt;code&gt;0&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;1. Change &lt;code&gt;file.txt&lt;/code&gt; permissions to &lt;code&gt;755&lt;/code&gt;:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;755 file.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows all users to read and execute the file, but only the owner to modify it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Change the &lt;code&gt;/var/www/html&lt;/code&gt; directory permissions to &lt;code&gt;755&lt;/code&gt;:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;755 /var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows all users to view the directory contents, but only the owner can add new files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Recursively apply permissions to all files and subdirectories using the &lt;code&gt;-R&lt;/code&gt; option:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; 755 /var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This enables &lt;code&gt;755&lt;/code&gt; permissions for all files and subdirectories in &lt;code&gt;/var/www/html&lt;/code&gt;, allowing all users to navigate and read/execute files, but only the owner can modify contents.&lt;/p&gt;

&lt;h3&gt;
  
  
  Change Permissions Using Symbolic Mode
&lt;/h3&gt;

&lt;p&gt;Symbolic mode uses letters for user classes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;u&lt;/code&gt;: File or directory owner.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;g&lt;/code&gt;: The group associated with a file or directory.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;o&lt;/code&gt;: Other system users who are neither the owner nor part of the group.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;a&lt;/code&gt;: All users, including owner, group, and others.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And operators to assign, remove, or set permissions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;+&lt;/code&gt;: Adds a permission.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-&lt;/code&gt;: Removes a permission.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;=&lt;/code&gt;: Explicitly sets the permission and overwrites the active permissions.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;userclass][operator][permissions] &lt;span class="o"&gt;[&lt;/span&gt;filename or directory]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;1. Grant the owner read, write, and execute permissions on &lt;code&gt;file.txt&lt;/code&gt;:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;u+rwx file.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Remove execute permission from the group on the &lt;code&gt;/var/www/html&lt;/code&gt; directory:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;g-x /var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Enable read permissions for all users on &lt;code&gt;file.txt&lt;/code&gt;:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;a+r file.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Change the file owner's permission to read-only, removing write and execute:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;&lt;span class="nv"&gt;u&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;r file.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Apply permissions recursively on the &lt;code&gt;/var/www/html&lt;/code&gt; directory with &lt;code&gt;-R&lt;/code&gt;:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; a+rx /var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This adds read and execute permissions for all users on &lt;code&gt;/var/www/html&lt;/code&gt; and its contents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Modify multiple user categories in a single command:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;ug+rw file.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This adds read and write permissions to both the owner and group on &lt;code&gt;file.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Modify different permissions for multiple user classes in a single command:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;u+rwx,g+w,o+x file.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This enables read, write, and execute for the owner, write for the group, and execute for other users on &lt;code&gt;file.txt&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set Special Permissions (Sticky Bit, SUID, SGID)
&lt;/h2&gt;

&lt;p&gt;Sticky bit, Set User ID (SUID), and Set Group ID (SGID) are special permissions that let administrators control file and directory access in multi-user environments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sticky Bit
&lt;/h3&gt;

&lt;p&gt;Sticky bit applies only to directories and ensures that only the owner can rename, delete, or move files in a directory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Use the &lt;code&gt;+t&lt;/code&gt; option to enable sticky bit on the &lt;code&gt;/var/www/html&lt;/code&gt; directory:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +t /var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Or use the &lt;code&gt;1&lt;/code&gt; numeric prefix to enable sticky bit while setting permissions, e.g. &lt;code&gt;755&lt;/code&gt; with sticky bit:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;1755 /var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;1&lt;/code&gt;: Enables the sticky bit.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;7&lt;/code&gt;: Grants read, write, and execute permissions to the owner.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;5&lt;/code&gt;: Grants read and execute permissions to the group.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;5&lt;/code&gt;: Grants read and execute permissions to other users.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. List the directory permissions to verify the sticky bit change:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-ld&lt;/span&gt; /var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should be similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;drwxr-xr-t 2 user group 4096 Jan 1 12:00 /var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sticky bit is active based on the &lt;code&gt;t&lt;/code&gt; in the above output.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set User ID (SUID)
&lt;/h3&gt;

&lt;p&gt;SUID enables the execution of a file with the privileges of its owner instead of the active user running it — useful for executable files that may require elevated privileges.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Create a sample &lt;code&gt;hello.sh&lt;/code&gt; script:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;hello.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Use &lt;code&gt;u+s&lt;/code&gt; with &lt;code&gt;chmod&lt;/code&gt; to enable the SUID bit on &lt;code&gt;hello.sh&lt;/code&gt;:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;u+s hello.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows all users to execute &lt;code&gt;hello.sh&lt;/code&gt; with the permissions of the file owner.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Or use numeric mode by adding &lt;code&gt;4&lt;/code&gt; as the leading digit:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;4755 hello.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;4&lt;/code&gt;: Enables the SUID bit.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;7&lt;/code&gt;: Grants read, write, and execute permissions to the owner.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;5&lt;/code&gt;: Grants read and execute permissions to the group.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;5&lt;/code&gt;: Grants read and execute permissions to other users.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. View the &lt;code&gt;hello.sh&lt;/code&gt; permissions to verify the SUID bit is active:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; hello.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should be similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-rwsr-xr-x 1 root root 1024 Jan  15 20:43 hello.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SUID bit is active based on the &lt;code&gt;s&lt;/code&gt; value in the owner permissions (&lt;code&gt;rws&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Set Group ID (SGID)
&lt;/h3&gt;

&lt;p&gt;SGID applies to executable files and directories and enables file execution using the group's permissions instead of the user's.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Enable SGID on the &lt;code&gt;hello.sh&lt;/code&gt; script:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;g+s hello.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Enable SGID on the &lt;code&gt;/var/www/html&lt;/code&gt; directory:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;g+s /var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Use the &lt;code&gt;2&lt;/code&gt; numeric mode to enable SGID while setting &lt;code&gt;755&lt;/code&gt; permissions on &lt;code&gt;hello.sh&lt;/code&gt;:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;2755 hello.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;2&lt;/code&gt;: Enables the SGID bit.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;7&lt;/code&gt;: Grants read, write, and execute permissions to the owner.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;5&lt;/code&gt;: Grants read and execute permissions to the group.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;5&lt;/code&gt;: Grants read and execute permissions to other users.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. View the &lt;code&gt;hello.sh&lt;/code&gt; permissions and verify SGID is active:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; hello.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should be similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-rwxr-sr-x 1 user group 123456 Jan  1 12:00 hello.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SGID is active based on the &lt;code&gt;s&lt;/code&gt; in the group permissions (&lt;code&gt;-rwxr-sr-x&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. List the &lt;code&gt;/var/www/html&lt;/code&gt; permissions and verify SGID is active:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-ld&lt;/span&gt; /var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should be similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;drwxr-sr-x 2 user group 4096 Jan 1 12:00 /var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SGID is active based on the &lt;code&gt;s&lt;/code&gt; in the group's permissions (&lt;code&gt;drwxr-sr-x&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Change User and Group Ownership Permissions
&lt;/h2&gt;

&lt;p&gt;You can change ownership using the &lt;code&gt;chown&lt;/code&gt; command:&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;options] user:group &lt;span class="o"&gt;[&lt;/span&gt;filename/directory]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;1. Grant &lt;code&gt;linuxuser&lt;/code&gt; ownership of &lt;code&gt;file.txt&lt;/code&gt;:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo chown &lt;/span&gt;linuxuser file.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sets &lt;code&gt;linuxuser&lt;/code&gt; as the owner while keeping group ownership unchanged.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Grant the &lt;code&gt;www-data&lt;/code&gt; group ownership of the &lt;code&gt;/var/www/html&lt;/code&gt; directory:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo chown&lt;/span&gt; :www-data /var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sets &lt;code&gt;www-data&lt;/code&gt; as the group owner while file ownership permissions remain unchanged.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Change both owner and group by separating them with &lt;code&gt;:&lt;/code&gt;:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo chown &lt;/span&gt;www-data:www-data /var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Recursively change owner and group for all files and subdirectories with &lt;code&gt;-R&lt;/code&gt;:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; www-data:www-data /var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Use &lt;code&gt;--reference&lt;/code&gt; to copy the ownership of one file to another:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;--reference&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/www/html file.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6. View the owner and group permissions to verify the changes:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; file.txt &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-ld&lt;/span&gt; /var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should be similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-rwxr-xr-- 1 www-data www-data 1024 Jan 1 12:00 file.txt
drwxr-xr-x 2 www-data www-data 4096 Jan 1 12:30 /var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Audit sensitive directories (like web roots and config folders) for overly permissive &lt;code&gt;777&lt;/code&gt; settings&lt;/li&gt;
&lt;li&gt;Combine SGID with shared group directories to keep team-owned files consistently group-writable&lt;/li&gt;
&lt;li&gt;Script routine permission and ownership checks with &lt;code&gt;find&lt;/code&gt; and &lt;code&gt;chmod&lt;/code&gt;/&lt;code&gt;chown&lt;/code&gt; for consistency across servers&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;man chmod&lt;/code&gt; and &lt;code&gt;man chown&lt;/code&gt; for the full set of options on your system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the full guide with additional tips, visit the original article on &lt;strong&gt;&lt;a href="https://docs.vultr.com/how-to-manage-file-and-directory-permissions-in-linux" rel="noopener noreferrer"&gt;Vultr Docs&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>security</category>
      <category>permissions</category>
      <category>sysadmin</category>
    </item>
    <item>
      <title>Managing Node Applications with PM2</title>
      <dc:creator>Sanskriti Harmukh</dc:creator>
      <pubDate>Wed, 14 Oct 2026 21:05:10 +0000</pubDate>
      <link>https://dev.to/vultr/managing-node-applications-with-pm2-39dn</link>
      <guid>https://dev.to/vultr/managing-node-applications-with-pm2-39dn</guid>
      <description>&lt;p&gt;Node.js is an open-source JavaScript runtime built on Chrome's V8 engine, known for its speed and scalability in real-time and high-performance applications. Running Node applications in production comes with unique challenges — you need to handle unexpected crashes, manage processes, and ensure continuous uptime. PM2 is a production process manager for Node.js applications that simplifies this, letting you monitor, restart, and optimize your apps. This guide walks through installing PM2, running a sample Node.js app under it, and configuring PM2 for production use. By the end, you'll have a Node.js application managed by PM2 with automatic restarts, centralized logs, and startup persistence configured.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;Access to your workstation as a non-root sudo user, running macOS, Linux, or Windows, with Node and npm installed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Install PM2 Using npm
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Install PM2 via the npm package manager:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;pm2 &lt;span class="nt"&gt;-g&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create an Example Node.js Application
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Navigate to your home directory:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Create a file named &lt;code&gt;app.js&lt;/code&gt; using a text editor such as &lt;code&gt;vim&lt;/code&gt;:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;vim app.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Add the following code to the file:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hostname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0.0.0.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/plain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello World&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Server running at http://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save and exit the file. This example app listens on port 3000 and responds to HTTP requests with the message "Hello World".&lt;/p&gt;

&lt;h2&gt;
  
  
  Start the Application Using PM2
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Start your Node.js application with PM2 (replace &lt;code&gt;app.js&lt;/code&gt; with your entry file if needed):&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pm2 start app.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should look similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[PM2] Starting /home/linuxuser/app.js in fork_mode (1 instance)
[PM2] Done.
┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐
│ id │ name               │ mode     │ ↺    │ status    │ cpu      │ memory   │
├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤
│ 0  │ app                │ fork     │ 0    │ online    │ 0%       │ 4.4mb    │
└────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useful additional parameters when starting an app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--name &amp;lt;app_name&amp;gt;&lt;/code&gt; - specify an application name&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--watch&lt;/code&gt; - restart your application automatically when a file changes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--max-memory-restart &amp;lt;memory&amp;gt;&lt;/code&gt; - a memory limit that triggers an automatic restart when reached&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--log &amp;lt;log_file&amp;gt;&lt;/code&gt; - specify a log file&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--restart-delay &amp;lt;delay&amp;gt;&lt;/code&gt; - delay between automatic restarts in milliseconds&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--time&lt;/code&gt; - prefix logs with time&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--no-autorestart&lt;/code&gt; - turn off automatic application restarts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--cron&lt;/code&gt; - a cron job pattern for automatic application restarts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--no-daemon&lt;/code&gt; - do not run the application in a daemon process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Visit the &lt;a href="https://pm2.keymetrics.io/docs/usage/application-declaration/#attributes-available" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; to view all available parameters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Manage the Application
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Restart your PM2-managed application:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pm2 restart app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Reload your PM2-managed application:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pm2 reload app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Stop your PM2-managed application:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pm2 stop app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Delete your PM2-managed application:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pm2 delete app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  List All Applications
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. List all the applications managed by PM2:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pm2 list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Display Logs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. View the logs produced by your application in real time:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pm2 logs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. View older logs by specifying the number of lines with the &lt;code&gt;--lines&lt;/code&gt; flag:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pm2 logs &lt;span class="nt"&gt;--lines&lt;/span&gt; 200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Monitor Resource Usage
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Monitor the resources consumed by your applications in near real time:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pm2 monit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Set Up a Web-Based Dashboard
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Configure a web-based PM2 dashboard:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pm2 plus
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Follow the prompts to create or log in to a PM2 account. If you don't have one, complete the required information and accept the license agreement. You'll see output during setup similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[PM2 I/O] Do you have a pm2.io account? (y/n) n
[PM2 I/O] No problem! We just need a few details to create your account.
[PM2 I/O] Please choose a username: exampleuser
[PM2 I/O] Please choose an email: admin@example.com
[PM2 I/O] Please choose a password: ************
...
[PM2 I/O] Do you accept the terms and privacy policy (https://pm2.io/legals/terms_conditions.pdf)? (y/n) y
[PM2 I/O] Successfully authenticated
[PM2 I/O] Successfully validated
[+] PM2+ activated!
[PM2 I/O] Successfully connected to bucket PM2 Plus Monitoring
[PM2 I/O] You can use the web interface here: https://app.pm2.io/#/bucket/67cb27ccd623d57f6d4c63c0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;exampleuser&lt;/code&gt; with your preferred username, &lt;code&gt;admin@example.com&lt;/code&gt; with your login email address, and provide a strong password to secure your PM2 account. Then open the provided web address, log in with your PM2 account credentials, and start monitoring your applications through the PM2 Plus dashboard.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;PM2 Plus is a subscription-based offering that incurs a cost depending on the number of processes you want to monitor. Review the pricing details on the PM2 Plus page.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Configure PM2 to Start on Server Boot
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Generate a startup script and configure PM2 to start on server boot:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pm2 startup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should look similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[PM2] To setup the Startup Script, copy/paste the following command:
sudo env PATH=$PATH:/home/linuxuser/.nvm/versions/node/v22.14.0/bin /home/linuxuser/.nvm/versions/node/v22.14.0/lib/node_modules/pm2/bin/pm2 startup systemd -u linuxuser --hp /home/linuxuser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Copy the command from the previous output and run it to make the PM2 process persistent:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo env &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;:/home/linuxuser/.nvm/versions/node/v22.14.0/bin /home/linuxuser/.nvm/versions/node/v22.14.0/lib/node_modules/pm2/bin/pm2 startup systemd &lt;span class="nt"&gt;-u&lt;/span&gt; linuxuser &lt;span class="nt"&gt;--hp&lt;/span&gt; /home/linuxuser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should look similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
[PM2] Writing init configuration in /etc/systemd/system/pm2-linuxuser.service
Created symlink /etc/systemd/system/multi-user.target.wants/pm2-linuxuser.service → /etc/systemd/system/pm2-linuxuser.service.
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure Applications to Start on Server Boot
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Save the running applications to the list:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pm2 save
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should look similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[PM2] Saving current process list...
[PM2] Successfully saved in /home/linuxuser/.pm2/dump.pm2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Restore the saved applications manually from the list if needed:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pm2 resurrect
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should look similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[PM2] Resurrecting
[PM2] Restoring processes located in /home/linuxuser/.pm2/dump.pm2
[PM2] Process /home/linuxuser/app.js restored
┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐
│ id │ name               │ mode     │ ↺    │ status    │ cpu      │ memory   │
├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤
│ 0  │ app                │ fork     │ 0    │ online    │ 0%       │ 21.3mb   │
└────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Check for Updates
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Print a list of outdated npm packages on your system:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm outdated &lt;span class="nt"&gt;-g&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This displays a list of globally installed npm packages that have newer versions available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update PM2
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Update the PM2 package via npm (this stops all running PM2 processes until the upgrade completes, causing downtime):&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;pm2@latest &lt;span class="nt"&gt;-g&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Perform an in-memory update of PM2:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pm2 update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Best Practices and Security Considerations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prevent Downtime With Cluster Mode
&lt;/h3&gt;

&lt;p&gt;Your PM2 processes may restart multiple times during their lifecycle, causing temporary downtime. Cluster mode runs multiple instances of your application, and when a restart is required, only one instance stops and restarts at a time while the others keep serving traffic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Delete the application before switching to cluster mode:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pm2 delete app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Start the application in cluster mode, specifying the number of instances with the &lt;code&gt;-i&lt;/code&gt; parameter:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pm2 start app.js &lt;span class="nt"&gt;-i&lt;/span&gt; 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This starts three instances of your application in cluster mode using PM2.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Scale the application by specifying the new number of instances:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pm2 scale app 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This scales your &lt;code&gt;app&lt;/code&gt; application up to &lt;strong&gt;4&lt;/strong&gt; instances.&lt;/p&gt;

&lt;h3&gt;
  
  
  Eliminate Failed Requests With Graceful Shutdown
&lt;/h3&gt;

&lt;p&gt;PM2 ensures graceful shutdowns by sending signals to your application so it can terminate cleanly, helping prevent data loss or corruption by allowing your app to finish active tasks before exiting:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Before shutdown, PM2 sends a &lt;code&gt;SIGINT&lt;/code&gt; signal to your application.&lt;/li&gt;
&lt;li&gt;Your application intercepts the signal.&lt;/li&gt;
&lt;li&gt;It finishes active requests and closes open resources like database connections.&lt;/li&gt;
&lt;li&gt;The application exits.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;1. Add the following code to enable graceful shutdown in your application:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SIGINT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// sigint signal received, log a message to the console&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Shutdown signal intercepted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// close the http server&lt;/span&gt;
    &lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// exit the process&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This intercepts the &lt;code&gt;SIGINT&lt;/code&gt; signal, logs a message to the console, closes the HTTP server, and exits the process cleanly.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Enable cluster mode in production to eliminate downtime during restarts&lt;/li&gt;
&lt;li&gt;Set up PM2 Plus (or another monitoring tool) for centralized visibility across multiple servers&lt;/li&gt;
&lt;li&gt;Add graceful shutdown handling to any application that holds open connections or resources&lt;/li&gt;
&lt;li&gt;Explore PM2's ecosystem file (&lt;code&gt;pm2.config.js&lt;/code&gt;) to manage multiple apps and environments from one file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the full guide with additional tips, visit the original article on &lt;strong&gt;&lt;a href="https://docs.vultr.com/how-to-manage-node-applications-with-pm2" rel="noopener noreferrer"&gt;Vultr Docs&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>node</category>
      <category>pm2</category>
      <category>javascript</category>
      <category>devops</category>
    </item>
    <item>
      <title>Viewing and Managing System Logs Using rsyslog in Linux</title>
      <dc:creator>Sanskriti Harmukh</dc:creator>
      <pubDate>Wed, 14 Oct 2026 21:04:30 +0000</pubDate>
      <link>https://dev.to/vultr/viewing-and-managing-system-logs-using-rsyslog-in-linux-52md</link>
      <guid>https://dev.to/vultr/viewing-and-managing-system-logs-using-rsyslog-in-linux-52md</guid>
      <description>&lt;p&gt;Linux systems generate a continuous stream of logs from the kernel, services, and user applications, and managing them is essential for diagnosing issues, tracking activity, and maintaining operational visibility. &lt;code&gt;rsyslog&lt;/code&gt; is a widely adopted logging daemon that collects, processes, stores, and optionally forwards these logs, supporting multiple input sources, advanced filtering, rule-based message routing, and remote logging capabilities beyond what basic syslog tools offer. This guide uses &lt;code&gt;rsyslog&lt;/code&gt; to view and manage system logs, define custom rules, and forward logs to remote servers for centralized logging. By the end, you'll have custom &lt;code&gt;rsyslog&lt;/code&gt; rules routing SSH and application logs to their own files, plus a client forwarding logs to a centralized remote server.&lt;/p&gt;




&lt;h2&gt;
  
  
  How &lt;code&gt;rsyslog&lt;/code&gt; Works: The Input, Filter, and Output Model
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;rsyslog&lt;/code&gt; processes log messages through a structured pipeline composed of three core stages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Input&lt;/strong&gt;: &lt;code&gt;rsyslog&lt;/code&gt; collects logs from various sources such as system sockets (&lt;code&gt;imuxsock&lt;/code&gt;), kernel messages (&lt;code&gt;imklog&lt;/code&gt;), plain-text files (&lt;code&gt;imfile&lt;/code&gt;), or network protocols like TCP/UDP (&lt;code&gt;imtcp&lt;/code&gt;, &lt;code&gt;imudp&lt;/code&gt;). Each input module defines how and where logs enter the system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filter&lt;/strong&gt;: After logs are received, &lt;code&gt;rsyslog&lt;/code&gt; evaluates them using filters based on facility, severity, or custom expression rules, which determines which logs are processed further and how they're handled.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output&lt;/strong&gt;: Filtered logs are routed to output destinations such as local log files, SQL databases, remote syslog servers, or message queues, each managed by a specific module with custom formatting and delivery rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common Log File Locations in Linux
&lt;/h2&gt;

&lt;p&gt;Log files are typically stored under &lt;code&gt;/var/log/&lt;/code&gt; and record system events, application activity, kernel operations, and security information. File names and purposes can vary slightly across distributions, but the following are among the most common:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/var/log/syslog&lt;/code&gt;: Captures general system activity, including messages from system services and applications. Commonly used on Debian and Ubuntu-based systems for broad troubleshooting.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/var/log/messages&lt;/code&gt;: A general-purpose log file used on Red Hat-based systems (like CentOS and Fedora), logging system messages not related to authentication or the kernel.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/var/log/auth.log&lt;/code&gt;: Records authentication attempts, including user logins, sudo actions, and SSH access. Essential for auditing and security monitoring.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/var/log/kern.log&lt;/code&gt;: Contains kernel-generated messages not handled by other facilities. Useful for diagnosing hardware issues or kernel module problems.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/var/log/dmesg&lt;/code&gt;: Stores the kernel ring buffer from the most recent boot, showing hardware detection and initialization messages.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/var/log/cron.log&lt;/code&gt;: Logs the execution of scheduled cron jobs, useful when debugging missed or failed automated tasks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  View Logs with Basic Linux Commands
&lt;/h2&gt;

&lt;p&gt;Before configuring &lt;code&gt;rsyslog&lt;/code&gt;, it's useful to know how to inspect log files with common Linux utilities.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;cat&lt;/strong&gt;: Outputs the entire contents of a file to the terminal. Best used for small log files.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &amp;lt;LOGFILE_PATH&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;more&lt;/strong&gt;: Displays the contents of a file one screen at a time. Use Space to scroll forward, q to quit.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;more &amp;lt;LOGFILE_PATH&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;less&lt;/strong&gt;: Opens the file in a scrollable viewer, letting you navigate forward and backward through large log files.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;less &amp;lt;LOGFILE_PATH&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;tail&lt;/strong&gt;: Displays the last 10 lines of a file by default. Useful for checking recent log entries.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;tail&lt;/span&gt; &amp;lt;LOGFILE_PATH&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;tail -f&lt;/strong&gt;: Continuously streams new log entries as they're written, ideal for real-time monitoring.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &amp;lt;LOGFILE_PATH&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;zcat, zless, zgrep&lt;/strong&gt;: Read or search compressed &lt;code&gt;.gz&lt;/code&gt; log files created by log rotation without decompressing them first.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;zless &amp;lt;GZ_LOGFILE_PATH&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Filter Logs Using &lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;awk&lt;/code&gt;, and &lt;code&gt;sed&lt;/code&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;grep&lt;/code&gt;&lt;/strong&gt;: Searches for specific keywords or regular expressions within log files, useful for locating errors, warnings, or other patterns.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"error"&lt;/span&gt; /var/log/syslog
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"failed"&lt;/span&gt; /var/log/auth.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;awk&lt;/code&gt;&lt;/strong&gt;: Processes log entries by splitting them into fields, useful for extracting timestamps, usernames, or specific columns from structured logs.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1, $2, $5}'&lt;/span&gt; /var/log/syslog
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;sed&lt;/code&gt;&lt;/strong&gt;: A stream editor that filters or transforms lines of text, useful for extracting a block of logs between two keywords or substituting text.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'/Start/,/End/p'&lt;/span&gt; /var/log/syslog
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/error/ERROR/g'&lt;/span&gt; /var/log/syslog
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configure &lt;code&gt;rsyslog&lt;/code&gt; Using &lt;code&gt;/etc/rsyslog.conf&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The behavior of &lt;code&gt;rsyslog&lt;/code&gt; is primarily controlled through its main configuration file at &lt;code&gt;/etc/rsyslog.conf&lt;/code&gt;, which defines how incoming log messages are processed, filtered, and directed based on their source (&lt;code&gt;facility&lt;/code&gt;), importance (&lt;code&gt;severity level&lt;/code&gt;), and destination (&lt;code&gt;action&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The general format for a rule is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FACILITY.LEVEL    ACTION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FACILITY&lt;/strong&gt;: The source of the log message (for example, &lt;code&gt;auth&lt;/code&gt;, &lt;code&gt;cron&lt;/code&gt;, &lt;code&gt;daemon&lt;/code&gt;, &lt;code&gt;kern&lt;/code&gt;, &lt;code&gt;mail&lt;/code&gt;, &lt;code&gt;user&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LEVEL&lt;/strong&gt;: The severity of the log message (for example, &lt;code&gt;debug&lt;/code&gt;, &lt;code&gt;info&lt;/code&gt;, &lt;code&gt;notice&lt;/code&gt;, &lt;code&gt;warning&lt;/code&gt;, &lt;code&gt;err&lt;/code&gt;, &lt;code&gt;crit&lt;/code&gt;, &lt;code&gt;alert&lt;/code&gt;, &lt;code&gt;emerg&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ACTION&lt;/strong&gt;: The destination for the log (for example, a file, remote server, or program).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Common Configuration Examples
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Send auth logs to a separate file:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="err"&gt;auth.*&lt;/span&gt;    &lt;span class="err"&gt;/var/log/auth.log&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Logs from the &lt;code&gt;auth&lt;/code&gt; facility (for example, SSH, sudo) go to &lt;code&gt;/var/log/auth.log&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Store kernel logs in a separate file:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="err"&gt;kern.*&lt;/span&gt;    &lt;span class="err"&gt;/var/log/kern.log&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;All kernel messages are redirected to &lt;code&gt;/var/log/kern.log&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Log only critical mail errors:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="err"&gt;mail.crit&lt;/span&gt;    &lt;span class="err"&gt;/var/log/mail_critical.log&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Only critical-level messages from the &lt;code&gt;mail&lt;/code&gt; facility are logged.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Log everything except mail and auth to a common file:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="err"&gt;*.info&lt;/span&gt;&lt;span class="c"&gt;;mail.none;authpriv.none    /var/log/general.log
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Sends all info-level or higher messages to &lt;code&gt;/var/log/general.log&lt;/code&gt;, except those from &lt;code&gt;mail&lt;/code&gt; and &lt;code&gt;authpriv&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Discard unwanted logs:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="err"&gt;*.debug&lt;/span&gt;    &lt;span class="err"&gt;~&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;The tilde (&lt;code&gt;~&lt;/code&gt;) discards all debug-level logs to reduce noise.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Log user messages to the console:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="err"&gt;user.*&lt;/span&gt;    &lt;span class="err"&gt;/dev/console&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Add Custom Logging Rules
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Log SSH Activity
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Create a new configuration file in &lt;code&gt;/etc/rsyslog.d/&lt;/code&gt;:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/rsyslog.d/10-ssh.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Add the following rule to capture all messages from the &lt;code&gt;authpriv&lt;/code&gt; facility:&lt;/strong&gt;&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="err"&gt;authpriv.*&lt;/span&gt;    &lt;span class="err"&gt;/var/log/ssh.log&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Restart the &lt;code&gt;rsyslog&lt;/code&gt; service to apply the new configuration:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;systemctl restart rsyslog.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SSH-related log entries are now written to &lt;code&gt;/var/log/ssh.log&lt;/code&gt;, making it easier to monitor authentication activity independently of other system logs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Log Activity for a Custom Application
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Create a new rsyslog configuration file in &lt;code&gt;/etc/rsyslog.d/&lt;/code&gt;:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/rsyslog.d/20-myapp.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Add the following rule to match logs from your application:&lt;/strong&gt;&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="err"&gt;if&lt;/span&gt; &lt;span class="err"&gt;($&lt;/span&gt;&lt;span class="py"&gt;programname&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;= 'myapp') then /var/log/myapp.log&lt;/span&gt;
&lt;span class="err"&gt;&amp;amp;&lt;/span&gt; &lt;span class="err"&gt;stop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Restart the rsyslog service to apply the changes:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;systemctl restart rsyslog.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All syslog messages from &lt;code&gt;myapp&lt;/code&gt; are now written to &lt;code&gt;/var/log/myapp.log&lt;/code&gt;, keeping application logs organized and separate from system logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Forward Logs to a Remote Server
&lt;/h2&gt;

&lt;p&gt;Centralizing logs from multiple systems on a single server helps streamline monitoring and troubleshooting. &lt;code&gt;rsyslog&lt;/code&gt; can forward logs from a client to a remote server over TCP or UDP.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure the Client and Server
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Create a configuration file on the client to forward all logs to the remote server:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/rsyslog.d/60-remote.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Add the forwarding rule to the file:&lt;/strong&gt;&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="err"&gt;*.*&lt;/span&gt; &lt;span class="err"&gt;@@192.2.0.3:514&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;192.2.0.3&lt;/code&gt; with your remote &lt;code&gt;rsyslog&lt;/code&gt; server's IP address. Use a single &lt;code&gt;@&lt;/code&gt; for UDP, or double &lt;code&gt;@@&lt;/code&gt; for TCP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. On the remote &lt;code&gt;rsyslog&lt;/code&gt; server, enable TCP log reception by editing the main config file:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/rsyslog.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Uncomment or add the following lines to enable TCP input:&lt;/strong&gt;&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="err"&gt;module(&lt;/span&gt;&lt;span class="py"&gt;load&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"imtcp")&lt;/span&gt;
&lt;span class="err"&gt;input(&lt;/span&gt;&lt;span class="py"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"imtcp"&lt;/span&gt; &lt;span class="s"&gt;port="514")&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. (Optional) Organize incoming logs by hostname and program name on the server:&lt;/strong&gt;&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="err"&gt;$template&lt;/span&gt; &lt;span class="err"&gt;RemoteLogs,"/var/log/remote/%HOSTNAME%/%PROGRAMNAME%.log"&lt;/span&gt;
&lt;span class="err"&gt;*.*&lt;/span&gt; &lt;span class="err"&gt;?RemoteLogs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This stores logs in structured folders like &lt;code&gt;/var/log/remote/client1/myapp.log&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Restart &lt;code&gt;rsyslog&lt;/code&gt; on both systems to apply the changes:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart rsyslog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After setup, the client system sends logs to the remote server, which organizes and stores them based on the defined templates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the Configuration
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Send a test log message:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;logger &lt;span class="nt"&gt;-p&lt;/span&gt; local0.info &lt;span class="s2"&gt;"Test message from rsyslog"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then check the appropriate log file (for example, &lt;code&gt;/var/log/syslog&lt;/code&gt; or your custom-defined log path) to confirm that the message was recorded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices for Using &lt;code&gt;rsyslog&lt;/code&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use separate files&lt;/strong&gt;: For better management, store logs from different services or applications in separate files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secure your logs&lt;/strong&gt;: Protect log files with proper file permissions to prevent unauthorized access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use log rotation&lt;/strong&gt;: Set up log rotation to avoid log files growing too large.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Centralized logging&lt;/strong&gt;: Use remote logging to collect logs from multiple systems in one place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor your logs&lt;/strong&gt;: Check log files often to identify and address issues promptly.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Set up &lt;code&gt;logrotate&lt;/code&gt; alongside &lt;code&gt;rsyslog&lt;/code&gt; to automatically archive and compress older log files.&lt;/li&gt;
&lt;li&gt;Ship logs from the remote &lt;code&gt;rsyslog&lt;/code&gt; server to a centralized SIEM or log aggregation platform.&lt;/li&gt;
&lt;li&gt;Add facility-specific rules for other services you run, following the same pattern used for SSH and custom applications.&lt;/li&gt;
&lt;li&gt;Automate alerting on critical log patterns using &lt;code&gt;grep&lt;/code&gt;/&lt;code&gt;awk&lt;/code&gt; pipelines or a log monitoring tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the full guide with additional tips, visit the original article on &lt;strong&gt;&lt;a href="https://docs.vultr.com/how-to-view-and-manage-system-logs-using-rsyslog-in-linux" rel="noopener noreferrer"&gt;Vultr Docs&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>rsyslog</category>
      <category>logging</category>
      <category>sysadmin</category>
    </item>
    <item>
      <title>Managing Dependencies with Poetry for a Flask App on Ubuntu 24.04</title>
      <dc:creator>Sanskriti Harmukh</dc:creator>
      <pubDate>Wed, 14 Oct 2026 21:03:57 +0000</pubDate>
      <link>https://dev.to/vultr/managing-dependencies-with-poetry-for-a-flask-app-on-ubuntu-2404-3fnd</link>
      <guid>https://dev.to/vultr/managing-dependencies-with-poetry-for-a-flask-app-on-ubuntu-2404-3fnd</guid>
      <description>&lt;p&gt;Poetry is an open-source tool for managing and packaging dependencies for Python projects. It maintains a clean view of your dependency tree, streamlines the installation and management of dependencies, and simplifies packaging and distribution to PyPI. This guide installs Poetry with Pipx on Ubuntu 24.04, creates a virtual environment, and runs a Flask application using Poetry and Gunicorn. By the end, you'll have a Flask app managed entirely through Poetry, tested with Pytest, and served in production mode with Gunicorn.&lt;/p&gt;




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

&lt;p&gt;Before you begin, you need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Have access to an Ubuntu 24.04 server.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Install Poetry with Pipx
&lt;/h2&gt;

&lt;p&gt;Installing Poetry in an isolated environment prevents conflicts between Poetry's internal dependencies and system-wide dependencies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Update the APT package index:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Install Pipx:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;pipx &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Add the Pipx libraries to your system's PATH:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pipx ensurepath
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Enable tab completions for Pipx commands:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'eval "$(register-python-argcomplete pipx)"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Reload the &lt;code&gt;.bashrc&lt;/code&gt; shell configuration to apply the changes in your active session:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6. Install Poetry using Pipx:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pipx &lt;span class="nb"&gt;install &lt;/span&gt;poetry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should be similar to the one below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;installed package poetry 2.0.0, installed using Python 3.12.3
  These apps are now globally available
    - poetry
done! ✨ 🌟 ✨
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;7. View the installed Poetry version:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poetry &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should be similar to the one below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Poetry (version 2.0.0)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create a Poetry Project
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Create a new &lt;code&gt;flask-app&lt;/code&gt; Poetry project:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poetry new flask-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a &lt;code&gt;flask-app&lt;/code&gt; directory with the following files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;flask_app&lt;/code&gt;: Contains an &lt;code&gt;__init__.py&lt;/code&gt; file that specifies all Flask code, including modules, functions, and other related support components.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pyproject.toml&lt;/code&gt;: Includes information about the project, Poetry configuration, and installed dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;README.md&lt;/code&gt;: Includes basic documentation about the project.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tests&lt;/code&gt;: Integrates and organizes the project's test cases.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your output should be similar to the one below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Created package flask_app in flask-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Switch to the &lt;code&gt;flask-app&lt;/code&gt; project directory:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;flask-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create a Virtual Environment and Install Flask
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Specify the Python interpreter for Poetry and create a new virtual environment:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poetry &lt;span class="nb"&gt;env &lt;/span&gt;use python3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This instructs Poetry to use the default Python3 interpreter on your system. You can also target a specific version, such as &lt;code&gt;poetry env use python3.11&lt;/code&gt;, if that version is installed.&lt;/p&gt;

&lt;p&gt;Your output should be similar to the one below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Creating virtualenv flask-app-vnQ3DaWl-py3.12 in /home/linuxuser/.cache/pypoetry/virtualenvs
Using virtualenv: /home/linuxuser/.cache/pypoetry/virtualenvs/flask-app-vnQ3DaWl-py3.12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Activate the virtual environment:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poetry &lt;span class="nb"&gt;env &lt;/span&gt;activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This outputs the command to run in order to activate the virtual environment manually. Your output should be similar to the one below:&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;source&lt;/span&gt; /home/linuxuser/.cache/pypoetry/virtualenvs/flask-app-vnQ3DaWl-py3.12/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Activate the virtual environment using its path:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; /home/linuxuser/.cache/pypoetry/virtualenvs/flask-app-vnQ3DaWl-py3.12/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should be similar to the one below when the virtual environment is activated:&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="o"&gt;(&lt;/span&gt;flask-app-py3.12&lt;span class="o"&gt;)&lt;/span&gt; linuxuser@:~/flask-app&lt;span class="err"&gt;$&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Install Flask using Poetry:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poetry add flask
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This installs Flask with all dependencies in the active virtual environment, adds the installed Flask version to &lt;code&gt;pyproject.toml&lt;/code&gt;, and creates a &lt;code&gt;poetry.lock&lt;/code&gt; file for deterministic builds.&lt;/p&gt;

&lt;p&gt;Your output should be similar to the one below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Using version ^3.1.0 for flask

Updating dependencies
Resolving dependencies... (0.2s)

Package operations: 7 installs, 0 updates, 0 removals

- Installing markupsafe (3.0.2)
- Installing blinker (1.9.0)
- Installing click (8.1.8)
- Installing itsdangerous (2.2.0)
- Installing jinja2 (3.1.5)
- Installing werkzeug (3.1.3)
- Installing flask (3.1.0)

Writing lock file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Deactivate the virtual environment:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deactivate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Set Up Firewall Rules
&lt;/h2&gt;

&lt;p&gt;Uncomplicated Firewall (UFW) is active on most Ubuntu 24.04 servers by default. Configure UFW to allow connections to the Flask application port &lt;code&gt;5000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Allow connections to the Flask application port &lt;code&gt;5000&lt;/code&gt;:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow 5000/tcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Reload UFW to apply the firewall configuration changes:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw reload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. View the UFW status and verify that the rule is active:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should be similar to the one below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Status: active

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW       Anywhere                  
5000/tcp                   ALLOW       Anywhere                  
22/tcp (v6)                ALLOW       Anywhere (v6)             
5000/tcp (v6)              ALLOW       Anywhere (v6)             
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Run the Flask App with Poetry
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Retrieve the command to reactivate the virtual environment:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poetry &lt;span class="nb"&gt;env &lt;/span&gt;activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should be similar to the one below:&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;source&lt;/span&gt; /home/linuxuser/.cache/pypoetry/virtualenvs/flask-app-vnQ3DaWl-py3.12/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Activate the virtual environment:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; /home/linuxuser/.cache/pypoetry/virtualenvs/flask-app-vnQ3DaWl-py3.12/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Navigate to the &lt;code&gt;flask_app&lt;/code&gt; directory:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;flask_app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Create a file &lt;code&gt;app.py&lt;/code&gt; using nano:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nano app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Add the following code:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;home&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;h1&amp;gt;Hello from Flask&amp;lt;/h1&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;5000&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the file with Ctrl+X, then Y, then Enter. This creates a webpage that returns &lt;strong&gt;Hello from Flask&lt;/strong&gt; on the home route &lt;code&gt;/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Run the Flask application:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poetry run python app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should be similar to the one below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* Serving Flask app 'app'
* Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://&amp;lt;SERVER-IP&amp;gt;:5000
Press CTRL+C to quit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;7. Access your Flask app using the following address in a web browser. Replace &lt;code&gt;&amp;lt;SERVER-IP&amp;gt;&lt;/code&gt; with your server's IP address:&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;http://&amp;lt;SERVER-IP&amp;gt;:5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxbgpekkowfq2zf5gsyew.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxbgpekkowfq2zf5gsyew.png" alt="Flask application home page" width="799" height="258"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. To stop the Flask app, press Ctrl+C in your terminal session.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Create Group Dependencies
&lt;/h2&gt;

&lt;p&gt;Poetry allows you to manage dependencies using groups. Follow these steps to create a group called &lt;code&gt;dev&lt;/code&gt; and install Pytest for testing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Install Pytest in the &lt;code&gt;dev&lt;/code&gt; dependency group:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poetry add pytest &lt;span class="nt"&gt;--group&lt;/span&gt; dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a new section in &lt;code&gt;pyproject.toml&lt;/code&gt; — &lt;code&gt;[tool.poetry.group.dev.dependencies]&lt;/code&gt; — which includes dependencies such as &lt;code&gt;pytest = "^8.3.4"&lt;/code&gt; under the &lt;code&gt;dev&lt;/code&gt; group. Production dependencies should not be added to any group; they belong under the root dependencies (&lt;code&gt;[tool.poetry.dependencies]&lt;/code&gt;) instead, so they aren't excluded during deployment or packaging.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Navigate to your project's &lt;code&gt;tests&lt;/code&gt; directory:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/flask-app/tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Create a new directory named &lt;code&gt;unit&lt;/code&gt;:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;unit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Navigate into the &lt;code&gt;unit&lt;/code&gt; directory:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;unit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Create an &lt;code&gt;__init__.py&lt;/code&gt; file:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;__init__.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This marks the directory as a Python package, allowing you to import modules from it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Create a test file &lt;code&gt;test_app.py&lt;/code&gt; using nano:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nano test_app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;7. Add the following code to the file:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask_app.app&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;  

&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_the_home_route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;h1&amp;gt;Hello from Flask&amp;lt;/h1&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the file with Ctrl+X, then Y, then Enter. This checks that the response body from &lt;code&gt;app.py&lt;/code&gt; contains the markup &lt;code&gt;&amp;lt;h1&amp;gt;Hello from Flask&amp;lt;/h1&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. Run the test:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pytest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should be similar to the one below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;platform linux -- Python 3.12.3, pytest-8.3.4, pluggy-1.5.0
rootdir: /home/linuxuser/flask-app
configfile: pyproject.toml
collected 1 item                                                             

test_app.py .                                                          [100%]

============================= 1 passed in 0.11s ==============================
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Install Gunicorn, a WSGI HTTP Server, with Flask
&lt;/h2&gt;

&lt;p&gt;A WSGI HTTP server such as Gunicorn handles client requests concurrently and efficiently, making it ideal for production-grade Flask applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Navigate to the &lt;code&gt;flask_app&lt;/code&gt; directory:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/flask-app/flask_app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Add Gunicorn as a root dependency:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poetry add gunicorn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Create a &lt;code&gt;wsgi.py&lt;/code&gt; file using nano:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nano wsgi.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Add the following Python code:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Run the Flask app with Gunicorn:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;gunicorn &lt;span class="nt"&gt;--bind&lt;/span&gt; 0.0.0.0:5000 wsgi:app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6. Check that your Flask app is still accessible:&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;http://&amp;lt;SERVER-IP&amp;gt;:5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxbgpekkowfq2zf5gsyew.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxbgpekkowfq2zf5gsyew.png" alt="Flask application home page" width="799" height="258"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Press Ctrl+C to stop the server.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Manage the Virtual Environment
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Exit the virtual environment:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deactivate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. List all virtual environments for your project:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poetry &lt;span class="nb"&gt;env &lt;/span&gt;list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The environment labeled &lt;code&gt;(Activated)&lt;/code&gt; is the one in use. Your output should be similar to the one below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flask-app-vnQ3DaWl-py3.12 (Activated)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Copy the environment name from the previous command's output and remove the virtual environment:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poetry &lt;span class="nb"&gt;env &lt;/span&gt;remove flask-app-vnQ3DaWl-py3.12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should be similar to the one below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Deleted virtualenv: /home/linuxuser/.cache/pypoetry/virtualenvs/flask-app-vnQ3DaWl-py3.12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Recreate the virtual environment:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poetry &lt;span class="nb"&gt;env &lt;/span&gt;use python3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Retrieve the command to reactivate the virtual environment:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poetry &lt;span class="nb"&gt;env &lt;/span&gt;activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should be similar to the one below:&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;source&lt;/span&gt; /home/linuxuser/.cache/pypoetry/virtualenvs/flask-app-vnQ3DaWl-py3.12/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6. Activate the virtual environment by copying and running the activation command from the previous step:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; /home/linuxuser/.cache/pypoetry/virtualenvs/flask-app-vnQ3DaWl-py3.12/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;7. Install all the dependencies from the &lt;code&gt;pyproject.toml&lt;/code&gt; file:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poetry &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the &lt;code&gt;pyproject.toml&lt;/code&gt; file present, the virtual environment and dependencies can always be recreated and reinstalled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modify the Virtual Environment Location
&lt;/h2&gt;

&lt;p&gt;By default, Poetry creates a virtual environment in &lt;code&gt;/.cache/pypoetry/virtualenvs&lt;/code&gt;. To keep the virtual environment organized alongside your project files, you can move its location to the root of your project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Exit the virtual environment:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;deactivate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. List the virtual environments for your project:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poetry &lt;span class="nb"&gt;env &lt;/span&gt;list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should be similar to the one below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flask-app-vnQ3DaWl-py3.12 (Activated)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Remove the virtual environment:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poetry &lt;span class="nb"&gt;env &lt;/span&gt;remove flask-app-vnQ3DaWl-py3.12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Configure Poetry to allow the virtual environment to be created at the root of your project:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poetry config virtualenvs.in-project &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This overwrites the default configuration set by Poetry and allows the virtual environment to be created in the root of your project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Verify that the configuration change is applied:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poetry config virtualenvs.in-project 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should be similar to the one below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6. Create a virtual environment to verify:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poetry &lt;span class="nb"&gt;env &lt;/span&gt;use python3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should be similar to the one below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Creating virtualenv flask-app in /home/linuxuser/flask-app/.venv
Using virtualenv: /home/linuxuser/flask-app/.venv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;7. Retrieve the command to activate the virtual environment:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poetry &lt;span class="nb"&gt;env &lt;/span&gt;activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should be similar to the one below:&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;source&lt;/span&gt; /home/linuxuser/flask-app/.venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;8. Activate the virtual environment:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; /home/linuxuser/flask-app/.venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;9. View your virtual environment information:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poetry &lt;span class="nb"&gt;env &lt;/span&gt;info
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should be similar to the one below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Virtualenv
Python:         3.12.3
Implementation: CPython
Path:           /home/linuxuser/flask-app/.venv
Executable:     /home/linuxuser/flask-app/.venv/bin/python
Valid:          True

Base
Platform:   linux
OS:         posix
Python:     3.12.3
Path:       /usr
Executable: /usr/bin/python3.12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;10. Install all the dependencies in this new environment:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poetry &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;11. Run the Flask app using Gunicorn to verify that everything is running in the new environment:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;gunicorn &lt;span class="nt"&gt;--bind&lt;/span&gt; 0.0.0.0:5000 wsgi:app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;12. Check that your Flask app is accessible:&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;http://&amp;lt;SERVER-IP&amp;gt;:5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxbgpekkowfq2zf5gsyew.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxbgpekkowfq2zf5gsyew.png" alt="Flask application home page" width="799" height="258"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Put Gunicorn behind a reverse proxy such as Nginx and run it as a systemd service for production deployments.&lt;/li&gt;
&lt;li&gt;Add more Poetry dependency groups (for example, &lt;code&gt;lint&lt;/code&gt; or &lt;code&gt;docs&lt;/code&gt;) to keep tooling separate from runtime dependencies.&lt;/li&gt;
&lt;li&gt;Wire the Pytest suite into a CI pipeline so tests run automatically on every push.&lt;/li&gt;
&lt;li&gt;Publish the package to PyPI or a private repository once it's ready to share.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the full guide with additional tips, visit the original article on &lt;strong&gt;&lt;a href="https://docs.vultr.com/how-to-manage-dependencies-with-poetry-for-a-flask-app-on-ubuntu-2404" rel="noopener noreferrer"&gt;Vultr Docs&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>poetry</category>
      <category>flask</category>
      <category>ubuntu</category>
    </item>
    <item>
      <title>Installing and Using nftables in Linux</title>
      <dc:creator>Sanskriti Harmukh</dc:creator>
      <pubDate>Wed, 14 Oct 2026 21:03:13 +0000</pubDate>
      <link>https://dev.to/vultr/installing-and-using-nftables-in-linux-30kn</link>
      <guid>https://dev.to/vultr/installing-and-using-nftables-in-linux-30kn</guid>
      <description>&lt;p&gt;&lt;code&gt;nftables&lt;/code&gt; is a modern packet filtering framework for Linux that replaces iptables. It offers a unified and efficient way to manage firewall rules across multiple protocols like IPv4, IPv6, and ARP, with better performance and a simpler configuration through a single interface instead of multiple tables. This guide installs and configures &lt;code&gt;nftables&lt;/code&gt; on Linux, sets up basic firewall rules, manages network traffic, and implements common security policies. By the end, you'll have a persistent &lt;code&gt;nftables&lt;/code&gt; firewall with SSH, HTTP, and HTTPS access rules, NAT/port forwarding, and logging in place.&lt;/p&gt;




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

&lt;p&gt;Before you begin, you need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Have access to a Linux-based instance as a non-root sudo user.&lt;/li&gt;
&lt;li&gt;Have a basic understanding of network protocols and firewall rule management.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;nftables&lt;/code&gt; versus &lt;code&gt;iptables&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;iptables&lt;/code&gt; is the traditional firewall management tool on Linux, but its limitations include a complex rule structure, performance inefficiencies, and the need for separate tools to handle different protocols (IPv4, IPv6, ARP, and so on). &lt;code&gt;nftables&lt;/code&gt; was introduced as a replacement, offering improvements in functionality, performance, and usability:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;&lt;code&gt;iptables&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;nftables&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Packet Filtering&lt;/td&gt;
&lt;td&gt;Requires multiple tools to manage different protocols (iptables, ip6tables, arptables)&lt;/td&gt;
&lt;td&gt;Uses a unified approach with named tables and chains&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rule Syntax&lt;/td&gt;
&lt;td&gt;Forces users to write complex chain-based rules with multiple entries&lt;/td&gt;
&lt;td&gt;Offers a modern, table-based syntax that simplifies rule writing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;Processes rules sequentially, which can slow down large rule sets&lt;/td&gt;
&lt;td&gt;Uses optimized rule evaluation, reducing overhead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Atomic Rule Changes&lt;/td&gt;
&lt;td&gt;Requires flushing and reloading entire rule sets for modifications&lt;/td&gt;
&lt;td&gt;Supports atomic rule updates, preventing disruption&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logging&lt;/td&gt;
&lt;td&gt;Uses legacy logging targets (LOG and ULOG frameworks)&lt;/td&gt;
&lt;td&gt;Provides built-in logging with advanced tracing options (&lt;code&gt;meta nftrace&lt;/code&gt;, &lt;code&gt;log&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connection Tracking&lt;/td&gt;
&lt;td&gt;Requires external &lt;code&gt;conntrack&lt;/code&gt; modules&lt;/td&gt;
&lt;td&gt;Integrates connection tracking directly into the system&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-protocol Support&lt;/td&gt;
&lt;td&gt;Requires different commands for each protocol&lt;/td&gt;
&lt;td&gt;Manages all protocols with a single command set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resource Usage&lt;/td&gt;
&lt;td&gt;Consumes more memory by duplicating rules&lt;/td&gt;
&lt;td&gt;Manages memory efficiently with a unified ruleset&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scripting &amp;amp; Automation&lt;/td&gt;
&lt;td&gt;Relies on shell scripting and manual rule management&lt;/td&gt;
&lt;td&gt;Supports JSON-based rule definition and direct scripting integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compatibility&lt;/td&gt;
&lt;td&gt;Runs on all distributions but follows a legacy architecture&lt;/td&gt;
&lt;td&gt;Works natively on modern systems while supporting &lt;code&gt;iptables&lt;/code&gt; conversion&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Install &lt;code&gt;nftables&lt;/code&gt; on Linux
&lt;/h2&gt;

&lt;p&gt;Install &lt;code&gt;nftables&lt;/code&gt; using the package manager for your Linux distribution.&lt;/p&gt;

&lt;h3&gt;
  
  
  On Ubuntu and Debian
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Update the server's package index:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Install &lt;code&gt;nftables&lt;/code&gt; using the APT package manager:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;nftables &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  On Rocky Linux and AlmaLinux
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Update the DNF package index:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Install &lt;code&gt;nftables&lt;/code&gt;:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; nftables
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  On Arch Linux
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Install &lt;code&gt;nftables&lt;/code&gt; using Pacman:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;pacman &lt;span class="nt"&gt;-S&lt;/span&gt; nftables
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;nft&lt;/code&gt; Command Syntax
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nft [ -nNscaeSupyjtT ] [ -I directory ] [ -f filename | -i | cmd ... ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-n&lt;/code&gt;: Show rule numbers in output.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-N&lt;/code&gt;: Print more detailed output for rules.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-s&lt;/code&gt;: Show statistics for rules.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-c&lt;/code&gt;: Show chain name for each rule.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-a&lt;/code&gt;: Print the rule with its address family and protocol.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-e&lt;/code&gt;: Expand rule details.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-S&lt;/code&gt;: Print the ruleset (configuration).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-u&lt;/code&gt;: Enable update mode for dynamic changes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-p&lt;/code&gt;: Print the current ruleset in a parsable format.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-y&lt;/code&gt;: Show detailed object types.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-j&lt;/code&gt;: Output in JSON format.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-t&lt;/code&gt;: Show output in table format.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-T&lt;/code&gt;: Show tokenized output.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-I directory&lt;/code&gt;: Use the specified directory for configuration files.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-f filename&lt;/code&gt;: Load rules from a file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-i&lt;/code&gt;: Interactive mode to modify rules directly.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cmd ...&lt;/code&gt;: Command arguments to manipulate the nftables rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Manage the &lt;code&gt;nftables&lt;/code&gt; System Service
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;nftables&lt;/code&gt; service manages your firewall rules and ensures persistence across system reboots.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Enable the &lt;code&gt;nftables&lt;/code&gt; service to start at boot:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;nftables
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Start the &lt;code&gt;nftables&lt;/code&gt; service:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start nftables
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Verify the service status:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status nftables
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should be similar to the one below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;● nftables.service - Netfilter Tables
     Loaded: loaded (/usr/lib/systemd/system/nftables.service; enabled; preset: disabled)
     Active: active (exited) since Mon 2025-03-31 17:52:35 UTC; 5s ago
       Docs: man:nft(8)
    Process: 1838 ExecStart=/sbin/nft -f /etc/sysconfig/nftables.conf (code=exited, status=0/SUCCESS)
   Main PID: 1838 (code=exited, status=0/SUCCESS)
        CPU: 11ms

Mar 31 17:52:35 nftables-03 systemd[1]: Starting Netfilter Tables...
Mar 31 17:52:35 nftables-03 systemd[1]: Finished Netfilter Tables.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tables, Chains, and Policies in &lt;code&gt;nftables&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;In &lt;code&gt;nftables&lt;/code&gt;, rules are organized into &lt;strong&gt;tables&lt;/strong&gt;, which contain &lt;strong&gt;chains&lt;/strong&gt; that define how traffic is processed. Each chain is associated with a &lt;strong&gt;hook&lt;/strong&gt; (&lt;code&gt;input&lt;/code&gt;, &lt;code&gt;output&lt;/code&gt;, &lt;code&gt;forward&lt;/code&gt;) that determines when it is evaluated, and chains have &lt;strong&gt;policies&lt;/strong&gt; that define the default action when no explicit rule matches a packet.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;nftables&lt;/code&gt; supports multiple address families for handling different network protocols:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;inet&lt;/code&gt;: Unified handling for both IPv4 and IPv6.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ip&lt;/code&gt;: IPv4-only filtering.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ip6&lt;/code&gt;: IPv6-only filtering.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;arp&lt;/code&gt;: ARP packet filtering.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bridge&lt;/code&gt;: Ethernet bridge packet filtering.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;netdev&lt;/code&gt;: Packet filtering at the network device level.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each chain is linked to a specific traffic type: &lt;code&gt;input&lt;/code&gt; controls packets destined for the local machine, &lt;code&gt;output&lt;/code&gt; manages packets leaving the machine, and &lt;code&gt;forward&lt;/code&gt; handles packets routed through the machine.&lt;/p&gt;

&lt;p&gt;The default policy determines whether unmatched traffic is accepted, dropped, or rejected:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;policy accept&lt;/code&gt; - Allows all traffic by default (useful for testing or initial configurations).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;policy drop&lt;/code&gt; - Discards unmatched packets (most secure, commonly used in production environments).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;policy reject&lt;/code&gt; - Sends a rejection notice to the source (informative but reveals the presence of the server, which might not be ideal for security).&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt; Using &lt;code&gt;accept&lt;/code&gt; as the default policy is not recommended for production environments, as it allows all traffic unless explicitly blocked.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Manage the Default &lt;code&gt;nftables&lt;/code&gt; Configuration
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. View the list of existing &lt;code&gt;nftables&lt;/code&gt; rules:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nft list tables
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should be similar to the one below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;table ip filter
table ip6 filter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. View the default &lt;code&gt;nftables&lt;/code&gt; configuration file:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/nftables.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should be similar to the one below:&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;#!/usr/sbin/nft -f
&lt;/span&gt;
&lt;span class="err"&gt;flush&lt;/span&gt; &lt;span class="err"&gt;ruleset&lt;/span&gt;

&lt;span class="err"&gt;table&lt;/span&gt; &lt;span class="err"&gt;inet&lt;/span&gt; &lt;span class="err"&gt;filter&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;chain&lt;/span&gt; &lt;span class="err"&gt;input&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
        &lt;span class="err"&gt;type&lt;/span&gt; &lt;span class="err"&gt;filter&lt;/span&gt; &lt;span class="err"&gt;hook&lt;/span&gt; &lt;span class="err"&gt;input&lt;/span&gt; &lt;span class="err"&gt;priority&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="c"&gt;; policy accept;
&lt;/span&gt;    &lt;span class="err"&gt;}&lt;/span&gt;
    &lt;span class="err"&gt;chain&lt;/span&gt; &lt;span class="err"&gt;forward&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
        &lt;span class="err"&gt;type&lt;/span&gt; &lt;span class="err"&gt;filter&lt;/span&gt; &lt;span class="err"&gt;hook&lt;/span&gt; &lt;span class="err"&gt;forward&lt;/span&gt; &lt;span class="err"&gt;priority&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="c"&gt;; policy accept;
&lt;/span&gt;    &lt;span class="err"&gt;}&lt;/span&gt;
    &lt;span class="err"&gt;chain&lt;/span&gt; &lt;span class="err"&gt;output&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
        &lt;span class="err"&gt;type&lt;/span&gt; &lt;span class="err"&gt;filter&lt;/span&gt; &lt;span class="err"&gt;hook&lt;/span&gt; &lt;span class="err"&gt;output&lt;/span&gt; &lt;span class="err"&gt;priority&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="c"&gt;; policy accept;
&lt;/span&gt;    &lt;span class="err"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure Basic &lt;code&gt;nftables&lt;/code&gt; Firewall Rules
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Create Initial Table and Chains
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Create a new firewall table for IPv4 and IPv6 traffic:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nft add table inet my_table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a new table named &lt;code&gt;my_table&lt;/code&gt; that can process both IPv4 and IPv6 packets using the &lt;code&gt;inet&lt;/code&gt; family. Replace &lt;code&gt;my_table&lt;/code&gt; with your preferred table name; it's used throughout the rest of this guide.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Add an input chain with a default accept policy:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nft add chain inet my_table input &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;type &lt;/span&gt;filter hook input priority 0 &lt;span class="se"&gt;\;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a filtering chain that applies to incoming traffic. &lt;code&gt;type filter&lt;/code&gt; designates it as a filtering chain, &lt;code&gt;hook input&lt;/code&gt; ensures it processes incoming packets, and &lt;code&gt;priority 0&lt;/code&gt; determines the execution order (lower values run first).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Create a forward chain with a default accept policy:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nft add chain inet my_table forward &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;type &lt;/span&gt;filter hook forward priority 0&lt;span class="se"&gt;\;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a filtering chain that applies to forwarded traffic, using &lt;code&gt;hook forward&lt;/code&gt; to process forwarded packets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Allow established connections to keep existing communications:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nft add rule inet my_table input ct state established,related accept
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This rule uses connection tracking: &lt;code&gt;ct state&lt;/code&gt; enables connection tracking, and &lt;code&gt;established&lt;/code&gt; allows packets from existing connections.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add, List, and Delete Rules
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Add a rule to block incoming traffic from a specific IP address:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nft add rule inet my_table input ip saddr 192.0.2.10 drop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This adds a rule to the &lt;code&gt;my_table&lt;/code&gt; table's &lt;code&gt;input&lt;/code&gt; chain, blocking incoming traffic from the IP address &lt;code&gt;192.0.2.10&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. View all active &lt;code&gt;nftables&lt;/code&gt; rules to verify configurations:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nft list ruleset
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. List the rules with handle numbers:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nft list chain inet my_table input
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output should be similar to the one below:&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="err"&gt;table&lt;/span&gt; &lt;span class="err"&gt;inet&lt;/span&gt; &lt;span class="err"&gt;my_table&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
        &lt;span class="err"&gt;chain&lt;/span&gt; &lt;span class="err"&gt;input&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
                &lt;span class="err"&gt;type&lt;/span&gt; &lt;span class="err"&gt;filter&lt;/span&gt; &lt;span class="err"&gt;hook&lt;/span&gt; &lt;span class="err"&gt;input&lt;/span&gt; &lt;span class="err"&gt;priority&lt;/span&gt; &lt;span class="err"&gt;filter&lt;/span&gt;&lt;span class="c"&gt;; policy accept;
&lt;/span&gt;                &lt;span class="err"&gt;ct&lt;/span&gt; &lt;span class="err"&gt;state&lt;/span&gt; &lt;span class="err"&gt;established,related&lt;/span&gt; &lt;span class="err"&gt;accept&lt;/span&gt;
                &lt;span class="err"&gt;ip&lt;/span&gt; &lt;span class="err"&gt;saddr&lt;/span&gt; &lt;span class="err"&gt;192.0.2.10&lt;/span&gt; &lt;span class="err"&gt;drop&lt;/span&gt;
        &lt;span class="err"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Identify the handle number from the output and delete the corresponding rule:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nft delete rule inet my_table input handle 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This deletes the rule with handle number &lt;code&gt;0&lt;/code&gt; from the &lt;code&gt;input&lt;/code&gt; chain of the &lt;code&gt;my_table&lt;/code&gt; table.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implement Essential Security Rules
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Allow SSH access to your server:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nft add rule inet my_table input tcp dport 22 accept
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matches TCP packets targeting destination port &lt;code&gt;22&lt;/code&gt; (the default SSH port) and accepts them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Block a specific IP address that shows suspicious activity:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nft add rule inet my_table input ip saddr 192.0.2.10 drop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matches packets from the specified source IP address and drops them without notifying the sender.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Allow HTTP traffic on port 80:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nft add rule inet my_table input tcp dport 80 accept
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Allow HTTPS traffic on port 443:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nft add rule inet my_table input tcp dport 443 accept
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Drop all other incoming traffic:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nft add rule inet my_table input ip daddr 0.0.0.0/0 counter drop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This drops all incoming traffic by default while allowing only the preconfigured rules (SSH, HTTP, HTTPS), effectively blocking unwanted access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enable NAT and Port Forwarding
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Enable NAT functionality in the Linux kernel by editing &lt;code&gt;/etc/sysctl.conf&lt;/code&gt;:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;vim /etc/sysctl.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Uncomment the following line:&lt;/strong&gt;&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="err"&gt;...&lt;/span&gt;
&lt;span class="c"&gt;# Uncomment the next line to enable packet forwarding for IPv4
&lt;/span&gt;&lt;span class="py"&gt;net.ipv4.ip_forward&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="err"&gt;....&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save and close the file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Apply the changes:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;sysctl &lt;span class="nt"&gt;-p&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Create a new NAT table to handle network address translation for your server:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nft add table nat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Create a postrouting chain in the NAT table to process packets after routing:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nft add chain nat postrouting &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;type &lt;/span&gt;nat hook postrouting priority 100 &lt;span class="se"&gt;\;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6. Add a masquerading rule for your internal network to enable internet connection sharing:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nft add rule nat postrouting ip saddr 192.0.2.0/24 oif enp1s0 masquerade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells &lt;code&gt;nftables&lt;/code&gt; to perform NAT (masquerading) for outgoing traffic from the &lt;code&gt;192.0.2.0/24&lt;/code&gt; network. The &lt;code&gt;oif enp1s0&lt;/code&gt; part specifies that the rule applies to traffic leaving the &lt;code&gt;enp1s0&lt;/code&gt; interface, typically the interface connected to the internet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Save Rules Persistently Across Reboots
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Save your current firewall configuration to the &lt;code&gt;/etc/nftables.conf&lt;/code&gt; file:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nft list ruleset | &lt;span class="nb"&gt;sudo tee&lt;/span&gt;  /etc/nftables.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Apply saved rules from the configuration file:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nft &lt;span class="nt"&gt;-f&lt;/span&gt; /etc/nftables.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Restart the &lt;code&gt;nftables&lt;/code&gt; service:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart nftables
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reloads the firewall rules and ensures they persist after reboots.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced Rule Configuration
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Implement rate limiting for SSH connections:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nft add rule inet my_table input tcp dport 22 limit rate 3/minute accept
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This targets SSH traffic (port 22), limits connections to 3 per minute, and accepts connections within the limit to help prevent brute force attacks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Create the prerouting chain in the nat table (if it doesn't exist):&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nft add chain ip nat prerouting &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;type &lt;/span&gt;nat hook prerouting priority 0 &lt;span class="se"&gt;\;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Add the port forwarding rule:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nft add rule nat prerouting tcp dport 80 dnat to 192.0.2.10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This captures incoming HTTP traffic (port 80) and forwards it to an internal server (&lt;code&gt;192.0.2.10&lt;/code&gt;), enabling you to host internal web services.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Enable logging for dropped packets:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nft &lt;span class="s1"&gt;'add rule inet my_table input log prefix "nftables-drop: " drop'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This logs packets before dropping them, with a custom prefix for easy filtering, and stores the logs in the system journal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Flush all the &lt;code&gt;nftables&lt;/code&gt; rules:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nft flush ruleset
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt; Be cautious when flushing the ruleset, as this removes all existing &lt;code&gt;nftables&lt;/code&gt; rules, effectively resetting your firewall configuration.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;ul&gt;
&lt;li&gt;Combine &lt;code&gt;nftables&lt;/code&gt; sets and maps to manage large groups of IP addresses or ports more efficiently.&lt;/li&gt;
&lt;li&gt;Script your &lt;code&gt;nftables&lt;/code&gt; ruleset into a version-controlled configuration file for repeatable deployments.&lt;/li&gt;
&lt;li&gt;Pair &lt;code&gt;nftables&lt;/code&gt; logging with a centralized log aggregator to monitor dropped and rejected traffic.&lt;/li&gt;
&lt;li&gt;Explore IPv6-specific rules using the &lt;code&gt;ip6&lt;/code&gt; address family for dual-stack environments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the full guide with additional tips, visit the original article on &lt;strong&gt;&lt;a href="https://docs.vultr.com/how-to-install-and-use-nftables-in-linux" rel="noopener noreferrer"&gt;Vultr Docs&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>nftables</category>
      <category>firewall</category>
      <category>networking</category>
    </item>
    <item>
      <title>Installing Docker with ROCm GPU Support on Ubuntu 24.04</title>
      <dc:creator>Sanskriti Harmukh</dc:creator>
      <pubDate>Wed, 14 Oct 2026 21:02:19 +0000</pubDate>
      <link>https://dev.to/vultr/installing-docker-with-rocm-gpu-support-on-ubuntu-2404-1oh6</link>
      <guid>https://dev.to/vultr/installing-docker-with-rocm-gpu-support-on-ubuntu-2404-1oh6</guid>
      <description>&lt;p&gt;Docker is a platform that packages applications and their dependencies into isolated containers so they run consistently on any system. GPU-enabled containers extend this by giving containerized applications access to a machine's Graphics Processing Unit (GPU) for compute-heavy workloads such as machine learning or video processing. This guide installs Docker from Docker's official repository on a ROCm-supported Ubuntu 24.04 system and accesses ROCm devices from inside a Docker container, assuming a GPU-enabled server with an AMD ROCm-capable GPU already set up. By the end, you'll have Docker installed and a temporary container running with verified access to the host's ROCm GPU devices.&lt;/p&gt;




&lt;h2&gt;
  
  
  Install Docker
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Add Docker's official GPG key to the system:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;ca-certificates curl
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo install&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 0755 &lt;span class="nt"&gt;-d&lt;/span&gt; /etc/apt/keyrings
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://download.docker.com/linux/ubuntu/gpg &lt;span class="nt"&gt;-o&lt;/span&gt; /etc/apt/keyrings/docker.asc
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;a+r /etc/apt/keyrings/docker.asc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These commands ensure that packages downloaded from Docker's repository are authentic and secure, which is needed before adding Docker's repository and installing the Docker engine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Add Docker's official repository to the system's APT sources list:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"deb [arch=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;dpkg &lt;span class="nt"&gt;--print-architecture&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;    $&lt;/span&gt;&lt;span class="s2"&gt;(. /etc/os-release &amp;amp;&amp;amp; echo "&lt;/span&gt;&lt;span class="nv"&gt;$VERSION_CODENAME&lt;/span&gt;&lt;span class="s2"&gt;") stable"&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="gp"&gt;    sudo tee /etc/apt/sources.list.d/docker.list &amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/dev/null
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Install Docker and its components:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This installs Docker Engine, the Docker CLI, Containerd, the Buildx plugin, and the Docker Compose plugin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understand the ROCm Devices Exposed to Containers
&lt;/h2&gt;

&lt;p&gt;Two host devices need to be exposed to Docker containers for GPU-accelerated workloads:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/dev/kfd&lt;/code&gt; &lt;strong&gt;(Kernel Fusion Driver)&lt;/strong&gt;: Manages communication between user applications and AMD GPUs for compute tasks. It also handles GPU memory, scheduling, and execution of computational tasks like deep learning or scientific computing.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/dev/dri&lt;/code&gt; &lt;strong&gt;(Direct Rendering Infrastructure)&lt;/strong&gt;: Provides access to the GPU for graphics rendering, and is also used for 2D/3D rendering and video processing tasks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both devices are required for exposing GPU capabilities to Docker containers for GPU-accelerated tasks such as machine learning and rendering. &lt;a href="https://rocm.docs.amd.com/projects/install-on-linux/en/latest/how-to/docker.html#accessing-gpus-in-containers" rel="noopener noreferrer"&gt;Learn more&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Access ROCm Devices from a Container
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Pull a lightweight ROCm terminal container image:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker pull rocm/rocm-terminal:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Run a temporary container with access to the GPU devices:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/kfd &lt;span class="nt"&gt;--device&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/dri &lt;span class="nt"&gt;--security-opt&lt;/span&gt; &lt;span class="nv"&gt;seccomp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;unconfined rocm/rocm-terminal 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command runs a temporary container with access to the necessary GPU devices (&lt;code&gt;/dev/kfd&lt;/code&gt; and &lt;code&gt;/dev/dri&lt;/code&gt;) on the host system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Verify GPU availability from inside the container:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rocm-smi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output should display all the detected GPU devices along with their specifications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Exit and destroy the container:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Install a machine learning framework such as PyTorch or TensorFlow with ROCm acceleration inside a container.&lt;/li&gt;
&lt;li&gt;Build a custom Dockerfile that layers your application on top of a ROCm base image.&lt;/li&gt;
&lt;li&gt;Orchestrate multiple ROCm-enabled containers with a container orchestrator such as Kubernetes.&lt;/li&gt;
&lt;li&gt;Monitor GPU utilization with &lt;code&gt;rocm-smi&lt;/code&gt; while running production workloads.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the full guide with additional tips, visit the original article on &lt;strong&gt;&lt;a href="https://docs.vultr.com/how-to-install-docker-with-rocm-gpu-support-on-ubuntu-24-04" rel="noopener noreferrer"&gt;Vultr Docs&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>rocm</category>
      <category>gpu</category>
      <category>ubuntu</category>
    </item>
    <item>
      <title>Installing K3s with ROCm GPU Operator on Ubuntu 24.04</title>
      <dc:creator>Sanskriti Harmukh</dc:creator>
      <pubDate>Wed, 14 Oct 2026 21:01:22 +0000</pubDate>
      <link>https://dev.to/vultr/installing-k3s-with-rocm-gpu-operator-on-ubuntu-2404-20pg</link>
      <guid>https://dev.to/vultr/installing-k3s-with-rocm-gpu-operator-on-ubuntu-2404-20pg</guid>
      <description>&lt;p&gt;K3s is a lightweight, certified Kubernetes distribution designed for resource-constrained environments such as edge computing and IoT devices. It simplifies deploying and managing Kubernetes clusters while staying compatible with standard Kubernetes tools and APIs. Paired with the ROCm GPU Operator, K3s can efficiently manage AMD GPUs within the cluster, automating GPU resource management and monitoring so you can deploy and scale GPU-accelerated workloads without manual configuration. This guide assumes a GPU-enabled Ubuntu 24.04 server with an AMD ROCm-compatible GPU and drivers already set up, and walks through installing K3s and Helm, then deploying cert-manager (for automated TLS certificate management) and the ROCm GPU Operator to enable AMD GPU management across the cluster. By the end, you'll have a K3s cluster with GPU resource management running.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Install and Configure K3s
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Install K3s:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="nt"&gt;-sfL&lt;/span&gt; https://get.k3s.io | sh -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Create a &lt;code&gt;.kube&lt;/code&gt; directory in your user home directory:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nv"&gt;$HOME&lt;/span&gt;/.kube
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Symlink the K3s config file as the default kubeconfig:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /etc/rancher/k3s/k3s.yaml &lt;span class="nv"&gt;$HOME&lt;/span&gt;/.kube/config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Change the config file permissions so Helm can load it:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;755 &lt;span class="nv"&gt;$HOME&lt;/span&gt;/.kube/config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Install Helm:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6. Check the K3s service status:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status k3s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;7. Enable the K3s service to start on boot:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;k3s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Install the ROCm GPU Operator
&lt;/h2&gt;

&lt;p&gt;This section installs cert-manager, a dependency for managing TLS certificates in Kubernetes, and deploys the &lt;a href="https://github.com/ROCm/gpu-operator/blob/main/docs/usage.md" rel="noopener noreferrer"&gt;ROCm GPU Operator&lt;/a&gt;, which handles GPU management in the cluster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Add the Helm repository for cert-manager:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;helm repo add jetstack https://charts.jetstack.io &lt;span class="nt"&gt;--force-update&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Install cert-manager:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;helm &lt;span class="nb"&gt;install &lt;/span&gt;cert-manager jetstack/cert-manager &lt;span class="nt"&gt;--namespace&lt;/span&gt; cert-manager &lt;span class="nt"&gt;--create-namespace&lt;/span&gt; &lt;span class="nt"&gt;--version&lt;/span&gt; v1.15.1 &lt;span class="nt"&gt;--set&lt;/span&gt; crds.enabled&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This installs cert-manager &lt;code&gt;v1.15.1&lt;/code&gt; — check the &lt;a href="https://cert-manager.io/docs/installation/" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; for the latest version before installing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Install the ROCm GPU Operator:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;helm &lt;span class="nb"&gt;install &lt;/span&gt;amd-gpu-operator &lt;span class="nt"&gt;--namespace&lt;/span&gt; kube-amd-gpu &lt;span class="nt"&gt;--create-namespace&lt;/span&gt; https://github.com/ROCm/gpu-operator/releases/download/v1.0.0/gpu-operator-charts-v1.0.0.tgz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This deploys the AMD GPU Operator into the &lt;code&gt;kube-amd-gpu&lt;/code&gt; namespace, enabling management and utilization of AMD GPUs in the cluster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. List the deployed Kubernetes resources:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;kubectl get all &lt;span class="nt"&gt;--namespace&lt;/span&gt; kube-amd-gpu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME                                                                  READY   STATUS    RESTARTS   AGE
pod/amd-gpu-operator-gpu-operator-charts-controller-manager-6bmkfwr   1/1     Running   0          6m
pod/amd-gpu-operator-kmm-controller-5f8d79b46-j4sbw                   1/1     Running   0          6m
pod/amd-gpu-operator-kmm-webhook-server-5d5fc8bdd6-p7qfs              1/1     Running   0          6m
pod/amd-gpu-operator-node-feature-discovery-gc-78989c896-lffs2        1/1     Running   0          6m
pod/amd-gpu-operator-node-feature-discovery-master-b8bffc48b-dtw7x    1/1     Running   0          6m
pod/amd-gpu-operator-node-feature-discovery-worker-m6jfz              1/1     Running   0          6m
.........
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pods can take 5–10 minutes to transition to a &lt;code&gt;Running&lt;/code&gt; status.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Deploy a ROCm-accelerated workload (PyTorch, TensorFlow, or a custom container) onto the cluster to confirm GPU scheduling works end to end.&lt;/li&gt;
&lt;li&gt;Look into running Docker with ROCm GPU support if you also need GPU access outside of Kubernetes.&lt;/li&gt;
&lt;li&gt;Monitor GPU utilization and health through the metrics the GPU Operator exposes.&lt;/li&gt;
&lt;li&gt;Review the &lt;a href="https://github.com/ROCm/gpu-operator/blob/main/docs/usage.md" rel="noopener noreferrer"&gt;ROCm GPU Operator usage docs&lt;/a&gt; for advanced scheduling and node-labeling options.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the full guide with additional tips, visit the original article on &lt;strong&gt;&lt;a href="https://docs.vultr.com/how-to-install-k3s-with-rocm-gpu-operator-on-ubuntu-24-04" rel="noopener noreferrer"&gt;Vultr Docs&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>k3s</category>
      <category>rocm</category>
      <category>gpu</category>
    </item>
    <item>
      <title>Installing phpMyAdmin on Ubuntu 24.04</title>
      <dc:creator>Sanskriti Harmukh</dc:creator>
      <pubDate>Wed, 14 Oct 2026 21:00:41 +0000</pubDate>
      <link>https://dev.to/vultr/installing-phpmyadmin-on-ubuntu-2404-5bcn</link>
      <guid>https://dev.to/vultr/installing-phpmyadmin-on-ubuntu-2404-5bcn</guid>
      <description>&lt;p&gt;phpMyAdmin is an open-source, web-based tool for managing MySQL databases. Installing it gives you a browser interface for creating, modifying, and deleting databases, tables, and fields, managing users, importing and exporting data, and running SQL statements without touching the command line. This guide installs MySQL and phpMyAdmin on Ubuntu 24.04, creates a sample database and user, and secures the phpMyAdmin endpoint with an extra layer of basic authentication. By the end, you'll have a working phpMyAdmin installation running over Apache, protected against unauthenticated access.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Set Up the MySQL Database Server
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Update the APT package index:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Install MySQL server:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;mysql-server &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Log in to the MySQL server:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;mysql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Create a sample database:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;my_company&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Create a new MySQL user with a strong password, replacing &lt;code&gt;your_password&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="s1"&gt;'company_admin'&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="s1"&gt;'localhost'&lt;/span&gt; &lt;span class="n"&gt;IDENTIFIED&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="s1"&gt;'your_password'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6. Grant the user full privileges on the database:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;my_company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'company_admin'&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="s1"&gt;'localhost'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;7. Flush privileges to apply the changes:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;FLUSH&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;8. Exit the MySQL console:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;EXIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Install phpMyAdmin
&lt;/h2&gt;

&lt;p&gt;phpMyAdmin is available in the default package repositories on Ubuntu 24.04.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Update the APT package index:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Install PHP and the required extensions:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;php php-mysql php-mbstring php-json php-xml php-curl php-zip php-common &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Install phpMyAdmin:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;phpmyadmin &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During setup, select &lt;code&gt;apache2&lt;/code&gt; as the web server to reconfigure automatically, keep the database configuration for phpMyAdmin set to &lt;code&gt;Yes&lt;/code&gt;, and set a strong phpMyAdmin database password when prompted (enter it twice to confirm).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Verify the installation succeeded:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;dpkg &lt;span class="nt"&gt;-l&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'/phpmyadmin / {print}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ii  phpmyadmin                           4:5.2.1+dfsg-3                          all          MySQL web administration tool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Configure phpMyAdmin
&lt;/h2&gt;

&lt;p&gt;phpMyAdmin ships with a default Apache virtual host configuration that serves the &lt;code&gt;/phpmyadmin&lt;/code&gt; endpoint using your server's IP or domain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Enable the phpMyAdmin virtual host configuration:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;a2enconf phpmyadmin.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Restart Apache to apply the configuration changes:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart apache2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Confirm Apache is running:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status apache2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Check the UFW status:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If UFW isn't installed, install it and allow SSH:&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;ufw &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow ssh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Allow the Apache Full profile through the firewall to enable HTTP and HTTPS:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow &lt;span class="s1"&gt;'Apache Full'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6. Reload UFW to apply the changes:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw reload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Access phpMyAdmin
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Open the phpMyAdmin endpoint in a browser using your server's IP address or domain name:&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;http://your_server_ip/phpmyadmin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Log in using the &lt;code&gt;company_admin&lt;/code&gt; credentials you created earlier, then select the &lt;code&gt;my_company&lt;/code&gt; database from the left navigation menu to manage it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Avoid logging in to phpMyAdmin with the MySQL &lt;code&gt;root&lt;/code&gt; account — accidental deletions, modifications, and security vulnerabilities are far more consequential from a superuser account. Always create a dedicated user scoped to the database(s) it actually needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Secure the phpMyAdmin Endpoint
&lt;/h2&gt;

&lt;p&gt;Basic authentication adds a second password prompt in front of the phpMyAdmin interface.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Create an &lt;code&gt;.htpasswd&lt;/code&gt; file with a new username, replacing &lt;code&gt;your_username&lt;/code&gt;:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;htpasswd &lt;span class="nt"&gt;-c&lt;/span&gt; /etc/phpmyadmin/.htpasswd your_username
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Open the phpMyAdmin Apache configuration:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/apache2/conf-available/phpmyadmin.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Add the &lt;code&gt;AllowOverride All&lt;/code&gt; directive inside the &lt;code&gt;/usr/share/phpmyadmin&lt;/code&gt; directory block, so it looks like this:&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;&amp;lt;Directory /usr/share/phpmyadmin&amp;gt;
    AllowOverride All
    ...
&amp;lt;/Directory&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Create a new &lt;code&gt;.htaccess&lt;/code&gt; file:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /usr/share/phpmyadmin/.htaccess
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Add the following authentication configuration to the file:&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;AuthType Basic
AuthName "Restricted Access to PhpMyAdmin"
AuthUserFile /etc/phpmyadmin/.htpasswd
Require valid-user
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;AuthType Basic&lt;/code&gt; sets the authentication type, &lt;code&gt;AuthName&lt;/code&gt; sets the message shown in the sign-in prompt, &lt;code&gt;AuthUserFile&lt;/code&gt; points to the file with allowed users and encrypted passwords, and &lt;code&gt;Require valid-user&lt;/code&gt; enforces that a valid user must authenticate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Restart Apache to apply the configuration changes:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart apache2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;7. Reload the phpMyAdmin URL and confirm you're prompted for the new username and password, then log in with your MySQL credentials to manage databases:&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;http://your_server_ip/phpmyadmin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Troubleshoot phpMyAdmin
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;phpMyAdmin login fails:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status mysql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Double-check your MySQL username and password, and review the config file for errors:&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/phpmyadmin/config.inc.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Internal Server Error:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/apache2/error.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reinstall the required PHP extensions if any are missing:&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;php-mysql php-mbstring php-json php-xml php-curl php-zip php-common &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Access Denied:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;mysql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="s1"&gt;'your_username'&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="s1"&gt;'localhost'&lt;/span&gt; &lt;span class="n"&gt;IDENTIFIED&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="s1"&gt;'your_password'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'your_username'&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="s1"&gt;'localhost'&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;OPTION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;FLUSH&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;EXIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Create additional MySQL users scoped to individual databases instead of reusing one admin account.&lt;/li&gt;
&lt;li&gt;Put phpMyAdmin behind HTTPS with a reverse proxy or Apache TLS configuration.&lt;/li&gt;
&lt;li&gt;Automate MySQL backups before making structural changes through phpMyAdmin.&lt;/li&gt;
&lt;li&gt;Review Apache and MySQL logs periodically for unusual login attempts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the full guide with additional tips, visit the original article on &lt;strong&gt;&lt;a href="https://docs.vultr.com/how-to-install-phpmyadmin-on-ubuntu-24-04" rel="noopener noreferrer"&gt;Vultr Docs&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>phpmyadmin</category>
      <category>mysql</category>
      <category>apache</category>
      <category>ubuntu</category>
    </item>
    <item>
      <title>Deploying Fluxer - An Open Source Instant Messaging and VoIP Platform</title>
      <dc:creator>Sanskriti Harmukh</dc:creator>
      <pubDate>Wed, 30 Sep 2026 19:54:43 +0000</pubDate>
      <link>https://dev.to/vultr/deploying-fluxer-an-open-source-instant-messaging-and-voip-platform-3o11</link>
      <guid>https://dev.to/vultr/deploying-fluxer-an-open-source-instant-messaging-and-voip-platform-3o11</guid>
      <description>&lt;p&gt;Fluxer is a free and open-source instant messaging and VoIP platform for friends, groups, and communities, providing messaging, communities, text and voice channels, media uploads, search, presence, and voice and video communication from a web-based interface. Fluxer ships an official Docker Compose stack that runs the web application, API, gateway, administration dashboard, messaging services, media services, PostgreSQL, Valkey, NATS, Meilisearch, SeaweedFS, LiveKit, and Caddy. This guide deploys Fluxer on a Linux server using that Docker Compose stack, exposes it over HTTPS with Caddy as a reverse proxy, and creates the owner account. By the end, you'll have a self-hosted Fluxer instance running securely at your domain with messaging and voice/video channels ready to use.&lt;/p&gt;




&lt;h2&gt;
  
  
  Set Up the Directory Structure and Environment Variables
&lt;/h2&gt;

&lt;p&gt;This section covers creating a working directory for Fluxer, downloading the official Docker Compose files, and configuring the environment variables required by the application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Create a new directory for Fluxer and move into it:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; ~/fluxer
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/fluxer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Download the official Docker Compose manifest:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="nt"&gt;-fsSLO&lt;/span&gt; https://raw.githubusercontent.com/fluxerapp/fluxer/3b1eb56713d59181823df284b9646c890d2a3cba/deploy/self-hosting/docker-compose.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Download the default Caddy configuration:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="nt"&gt;-fsSLO&lt;/span&gt; https://raw.githubusercontent.com/fluxerapp/fluxer/3b1eb56713d59181823df284b9646c890d2a3cba/deploy/self-hosting/Caddyfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Download the LiveKit configuration:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="nt"&gt;-fsSLO&lt;/span&gt; https://raw.githubusercontent.com/fluxerapp/fluxer/3b1eb56713d59181823df284b9646c890d2a3cba/deploy/self-hosting/livekit.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Download the example environment file and save it as &lt;code&gt;.env&lt;/code&gt;:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/fluxerapp/fluxer/3b1eb56713d59181823df284b9646c890d2a3cba/deploy/self-hosting/.env.example &lt;span class="nt"&gt;-o&lt;/span&gt; .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Fluxer directory now contains the following files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;Caddyfile&lt;/span&gt;
&lt;span class="s"&gt;docker-compose.yml&lt;/span&gt;
&lt;span class="s"&gt;livekit.yaml&lt;/span&gt;
&lt;span class="s"&gt;.env&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These files define the Fluxer containers, public routing, voice services, and application configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Open the &lt;code&gt;.env&lt;/code&gt; file:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nano .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set the following variables at the beginning of the file. Replace &lt;code&gt;fluxer.example.com&lt;/code&gt; with your Fluxer domain and &lt;code&gt;admin@example.com&lt;/code&gt; with your email address.&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="py"&gt;FLUXER_DOMAIN&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;fluxer.example.com&lt;/span&gt;
&lt;span class="py"&gt;FLUXER_CADDY_SITE_ADDRESS&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;fluxer.example.com&lt;/span&gt;
&lt;span class="py"&gt;FLUXER_VAPID_EMAIL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;admin@example.com&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save and close the file. &lt;code&gt;FLUXER_DOMAIN&lt;/code&gt; defines the public Fluxer address. &lt;code&gt;FLUXER_CADDY_SITE_ADDRESS&lt;/code&gt; specifies the hostname Caddy serves.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generate Fluxer Secrets
&lt;/h3&gt;

&lt;p&gt;Fluxer requires separate secrets for PostgreSQL, Meilisearch, object storage, internal RPC communication, the administration dashboard, media uploads, and LiveKit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Run the following loop to generate random hexadecimal values and insert them into &lt;code&gt;.env&lt;/code&gt;:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for &lt;/span&gt;key &lt;span class="k"&gt;in &lt;/span&gt;POSTGRES_PASSWORD MEILI_MASTER_KEY FLUXER_S3_SECRET_KEY FLUXER_SUDO_MODE_SECRET FLUXER_CONNECTION_INITIATION_SECRET FLUXER_GATEWAY_RPC_AUTH_TOKEN FLUXER_MEDIA_PROXY_SECRET_KEY FLUXER_ADMIN_SECRET_KEY_BASE FLUXER_ADMIN_OAUTH_CLIENT_SECRET LIVEKIT_API_SECRET&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"s|^&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="s2"&gt;=.*|&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="s2"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 32&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;|"&lt;/span&gt; .env&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Generate the media upload relay secret in Base64 format and insert it into the &lt;code&gt;.env&lt;/code&gt; file:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"s|^FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64=.*|FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-base64&lt;/span&gt; 32&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;|"&lt;/span&gt; .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Generate VAPID Keys
&lt;/h3&gt;

&lt;p&gt;Fluxer uses VAPID keys for web push notifications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Add your user to the &lt;code&gt;docker&lt;/code&gt; group to run Docker commands without &lt;code&gt;sudo&lt;/code&gt;:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker &lt;span class="nv"&gt;$USER&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Apply the group change to the current session:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;newgrp docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Generate a VAPID key pair using a temporary Node.js container:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; node:24-alpine npx &lt;span class="nt"&gt;--yes&lt;/span&gt; web-push generate-vapid-keys &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your output contains two values: &lt;code&gt;publicKey&lt;/code&gt; (VAPID public key) and &lt;code&gt;privateKey&lt;/code&gt; (VAPID private key). Copy the value displayed after &lt;code&gt;publicKey&lt;/code&gt; and &lt;code&gt;privateKey&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Add the public key to &lt;code&gt;.env&lt;/code&gt;:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s|^FLUXER_VAPID_PUBLIC_KEY=.*|FLUXER_VAPID_PUBLIC_KEY=YOUR_VAPID_PUBLIC_KEY|'&lt;/span&gt; .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;YOUR_VAPID_PUBLIC_KEY&lt;/code&gt; with the &lt;code&gt;publicKey&lt;/code&gt; value from your output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Add the private key to &lt;code&gt;.env&lt;/code&gt;:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s|^FLUXER_VAPID_PRIVATE_KEY=.*|FLUXER_VAPID_PRIVATE_KEY=YOUR_VAPID_PRIVATE_KEY|'&lt;/span&gt; .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;YOUR_VAPID_PRIVATE_KEY&lt;/code&gt; with the &lt;code&gt;privateKey&lt;/code&gt; value from your output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Restrict access to the &lt;code&gt;.env&lt;/code&gt; file so that only your account can read or modify it:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;600 .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;.env&lt;/code&gt; file contains the credentials and secrets for your Fluxer deployment. Do not upload it to a public repository or include its contents in screenshots.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Verify the public Fluxer settings:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'^(FLUXER_DOMAIN|FLUXER_CADDY_SITE_ADDRESS|FLUXER_VAPID_EMAIL)='&lt;/span&gt; .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&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;FLUXER_DOMAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fluxer.example.com
&lt;span class="nv"&gt;FLUXER_CADDY_SITE_ADDRESS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fluxer.example.com
&lt;span class="nv"&gt;FLUXER_VAPID_EMAIL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;admin@example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Deploy Fluxer Containers
&lt;/h2&gt;

&lt;p&gt;This section covers validating the Docker Compose configuration, deploying the Fluxer containers, and verifying that all services initialize correctly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Navigate to the Fluxer directory:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/fluxer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Validate the Docker Compose configuration:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker compose config &lt;span class="nt"&gt;--quiet&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command returns no output when the Compose configuration is valid.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Download the Fluxer container images:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker compose pull
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker downloads the Fluxer application images and supporting services required by the stack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Create and start the containers:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first deployment can take several minutes while Docker downloads images and Fluxer initializes its application, database, search, object storage, and messaging services.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Verify the containers:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker compose ps &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every service should show a status of &lt;code&gt;Up&lt;/code&gt;, and services with a health check also show &lt;code&gt;(healthy)&lt;/code&gt;. The &lt;code&gt;seaweedfs-init&lt;/code&gt; container exits after creating the required object-storage buckets — an exit code of &lt;code&gt;0&lt;/code&gt; for this container is expected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. View the Fluxer API logs:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker compose logs &lt;span class="nt"&gt;-f&lt;/span&gt; api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Press Ctrl+C after the API completes its startup process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. View recent logs from all Fluxer services:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker compose logs &lt;span class="nt"&gt;--tail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;8. If a container fails to start, view its logs using the service name:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker compose logs SERVICE_NAME
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;SERVICE_NAME&lt;/code&gt; with the failed service, such as &lt;code&gt;api&lt;/code&gt;, &lt;code&gt;gateway&lt;/code&gt;, or &lt;code&gt;livekit&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Configure Caddy as a Reverse Proxy to Expose Fluxer
&lt;/h2&gt;

&lt;p&gt;Fluxer's official Docker Compose stack includes Caddy as the reverse proxy. Caddy listens on ports &lt;code&gt;80&lt;/code&gt; and &lt;code&gt;443&lt;/code&gt;, automatically provisions HTTPS certificates for the hostname defined by &lt;code&gt;FLUXER_CADDY_SITE_ADDRESS&lt;/code&gt;, and forwards requests to the appropriate Fluxer containers. The API, gateway, media service, administration dashboard, LiveKit signaling, and web application are all available through the same public hostname.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Validate the Caddy configuration:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;caddy caddy validate &lt;span class="nt"&gt;--config&lt;/span&gt; /etc/caddy/Caddyfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A successful validation indicates that Caddy can load the configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. View the Caddy logs:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker compose logs &lt;span class="nt"&gt;--tail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100 caddy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Caddy automatically requests and renews TLS certificates when &lt;code&gt;fluxer.example.com&lt;/code&gt; resolves to your server and ports &lt;code&gt;80&lt;/code&gt; and &lt;code&gt;443&lt;/code&gt; are publicly reachable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Access and Configure Fluxer
&lt;/h2&gt;

&lt;p&gt;This section covers accessing the Fluxer web application, creating the initial owner account, and reviewing the main instance settings.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;code&gt;https://fluxer.example.com&lt;/code&gt; in your web browser.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Fluxer Set Up Welcome&lt;/strong&gt; page should appear. Select your preferred language from the &lt;strong&gt;Language&lt;/strong&gt; menu and click &lt;strong&gt;Next&lt;/strong&gt; to continue.&lt;/li&gt;
&lt;li&gt;Choose a theme for the Fluxer Admin interface and click &lt;strong&gt;Next&lt;/strong&gt; to continue.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Create the administrator account&lt;/strong&gt; page appears. Fluxer displays a message that the instance does not have any accounts yet and that the first account you create automatically becomes the administrator. Click &lt;strong&gt;Next&lt;/strong&gt; to continue.&lt;/li&gt;
&lt;li&gt;Configure the administrator account. Enter all required information and click &lt;strong&gt;Next&lt;/strong&gt; to create an account.&lt;/li&gt;
&lt;li&gt;Open a new tab in your web browser and access the Fluxer administration dashboard at &lt;code&gt;https://fluxer.example.com/admin&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Log in using the Fluxer account you created.&lt;/li&gt;
&lt;li&gt;Review the requested permissions required by the administration application. Keep the required scopes enabled and click &lt;strong&gt;Authorize&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Fluxer redirects you to the Admin dashboard after authorization. The dashboard displays administrative tools in the left navigation menu.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your Fluxer administrator account is now configured and ready to manage users, guilds, applications, moderation settings, and other instance-level features.&lt;/p&gt;




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

&lt;p&gt;Fluxer is running with messaging, voice and video channels, and media sharing available to your users behind HTTPS. From here you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create additional guilds and channels, and invite members to your community&lt;/li&gt;
&lt;li&gt;Review moderation and permission settings from the admin dashboard&lt;/li&gt;
&lt;li&gt;Configure object storage retention and monitor SeaweedFS, Meilisearch, and LiveKit resource usage as usage grows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the full guide with additional tips, visit the original article on &lt;strong&gt;&lt;a href="https://docs.vultr.com/how-to-deploy-fluxer-an-open-source-instant-messaging-and-voip-platform" rel="noopener noreferrer"&gt;Vultr Docs&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>messaging</category>
      <category>voip</category>
      <category>docker</category>
      <category>selfhosted</category>
    </item>
    <item>
      <title>Deploying Umami - Open-Source Analytics Platform</title>
      <dc:creator>Sanskriti Harmukh</dc:creator>
      <pubDate>Wed, 30 Sep 2026 19:54:18 +0000</pubDate>
      <link>https://dev.to/vultr/deploying-umami-open-source-analytics-platform-4gl7</link>
      <guid>https://dev.to/vultr/deploying-umami-open-source-analytics-platform-4gl7</guid>
      <description>&lt;p&gt;Umami is an open-source, privacy-focused web analytics platform designed as a self-hosted alternative to Google Analytics. It avoids cookies and personally identifiable information, removes the need for cookie banners, and captures real-time metrics including visitor devices, geographic data, referrers, and custom events through a lightweight tracking script. This guide deploys Umami on a Linux server using Docker Compose with PostgreSQL as the database, Redis for session caching, and Traefik for automatic HTTPS through Let's Encrypt, then walks through directory setup, environment configuration, and integrating the tracking script into a sample website. By the end, you'll have a self-hosted Umami instance tracking real traffic on your own domain.&lt;/p&gt;




&lt;h2&gt;
  
  
  Set Up the Directory Structure, Configuration, and Environment Variables
&lt;/h2&gt;

&lt;p&gt;Umami requires a project directory with persistent storage for the PostgreSQL database, Redis cache, and Let's Encrypt certificates. Environment variables control the domain, database credentials, application secret, and tracking customizations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Create the project directory and move into it:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/umami/&lt;span class="o"&gt;{&lt;/span&gt;pgdata,redis,letsencrypt&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/umami
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pgdata/&lt;/code&gt;: Persists PostgreSQL database files.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;redis/&lt;/code&gt;: Stores Redis data for caching and session management.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;letsencrypt/&lt;/code&gt;: Stores Traefik ACME certificates for HTTPS renewal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Generate a strong random secret for the application:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the output for use in the environment file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Create the environment file:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nano .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following configuration. Replace &lt;code&gt;umami.example.com&lt;/code&gt; with your domain name, &lt;code&gt;admin@example.com&lt;/code&gt; with your email address, &lt;code&gt;GENERATED_SECRET&lt;/code&gt; with the generated random string, and &lt;code&gt;STRONG_DATABASE_PASSWORD&lt;/code&gt; with a secure database password.&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;# Domain Configuration
&lt;/span&gt;&lt;span class="py"&gt;DOMAIN&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;umami.example.com&lt;/span&gt;
&lt;span class="py"&gt;LETSENCRYPT_EMAIL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;admin@example.com&lt;/span&gt;

&lt;span class="c"&gt;# Application Secret
&lt;/span&gt;&lt;span class="py"&gt;APP_SECRET&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;GENERATED_SECRET&lt;/span&gt;

&lt;span class="c"&gt;# PostgreSQL Configuration
&lt;/span&gt;&lt;span class="py"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;umami&lt;/span&gt;
&lt;span class="py"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;umami&lt;/span&gt;
&lt;span class="py"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;STRONG_DATABASE_PASSWORD&lt;/span&gt;
&lt;span class="py"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;postgresql://umami:${POSTGRES_PASSWORD}@postgres:5432/umami&lt;/span&gt;

&lt;span class="c"&gt;# Redis Configuration
&lt;/span&gt;&lt;span class="py"&gt;REDIS_URL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;redis://redis:6379&lt;/span&gt;

&lt;span class="c"&gt;# Tracker Customization
&lt;/span&gt;&lt;span class="py"&gt;TRACKER_SCRIPT_NAME&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;custom-stats&lt;/span&gt;
&lt;span class="py"&gt;COLLECT_API_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/custom-api/send&lt;/span&gt;
&lt;span class="py"&gt;DISABLE_TELEMETRY&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save and close the file.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The &lt;code&gt;TRACKER_SCRIPT_NAME&lt;/code&gt; and &lt;code&gt;COLLECT_API_ENDPOINT&lt;/code&gt; variables add an alternate path for the tracking script and collection endpoint, alongside the defaults, to help bypass ad blockers that specifically target &lt;code&gt;/script.js&lt;/code&gt;. The default paths remain active. &lt;code&gt;DISABLE_TELEMETRY=1&lt;/code&gt; prevents Umami from sending anonymous usage statistics.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Deploy with Docker Compose
&lt;/h2&gt;

&lt;p&gt;The deployment stack runs four services: Traefik as the reverse proxy with automatic TLS, PostgreSQL for analytics data persistence, Redis for session caching, and the Umami web application. This configuration is based on the &lt;a href="https://github.com/umami-software/umami/blob/master/docker-compose.yml" rel="noopener noreferrer"&gt;official Umami Docker Compose example&lt;/a&gt;, adapted to use Traefik and persistent storage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Create the Docker Compose manifest:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nano docker-compose.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;traefik&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik:v3.7.0&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;umami-traefik&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--providers.docker=true"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--providers.docker.exposedbydefault=false"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--entrypoints.web.address=:80"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--entrypoints.websecure.address=:443"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--entrypoints.web.http.redirections.entrypoint.to=websecure"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--entrypoints.web.http.redirections.entrypoint.scheme=https"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--certificatesresolvers.letsencrypt.acme.tlschallenge=true"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL}"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;80:80"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;443:443"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/run/docker.sock:/var/run/docker.sock:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./letsencrypt:/letsencrypt&lt;/span&gt;

  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:17-alpine&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;umami-postgres&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${POSTGRES_DB}&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${POSTGRES_USER}&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${POSTGRES_PASSWORD}&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./pgdata:/var/lib/postgresql/data&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pg_isready&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-d&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;${POSTGRES_DB}&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-U&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;${POSTGRES_USER}"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;

  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:8-alpine&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;umami-redis&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis-server"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--appendonly"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yes"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./redis:/data&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis-cli"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ping"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;

  &lt;span class="na"&gt;umami&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/umami-software/umami:3.1.0&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;umami&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.env&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
      &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
    &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.enable=true"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.umami.rule=Host(`${DOMAIN}`)"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.umami.entrypoints=websecure"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.umami.tls=true"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.umami.tls.certresolver=letsencrypt"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.services.umami.loadbalancer.server.port=3000"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save and close the file. &lt;code&gt;traefik&lt;/code&gt; handles reverse proxy and TLS termination, redirecting HTTP to HTTPS and provisioning Let's Encrypt certificates using the TLS-ALPN-01 challenge. &lt;code&gt;postgres&lt;/code&gt; runs PostgreSQL 17 as the primary database for analytics data, website configurations, and user accounts. &lt;code&gt;redis&lt;/code&gt; runs Redis 8 as the caching layer for server-side authentication sessions, reducing database load. &lt;code&gt;umami&lt;/code&gt; runs the official Umami application pinned to version &lt;code&gt;3.1.0&lt;/code&gt; and registers with Traefik for HTTPS routing on the configured domain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Start the services:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Verify that all containers are running:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker compose ps &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output displays four containers. &lt;code&gt;postgres&lt;/code&gt; and &lt;code&gt;redis&lt;/code&gt; show a healthy status; &lt;code&gt;traefik&lt;/code&gt; and &lt;code&gt;umami&lt;/code&gt; show as running, since those two have no configured health check.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. View the service logs to confirm Umami connected to the database:&lt;/strong&gt;&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;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker compose logs umami
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output displays Umami completing database migrations and reporting the application as ready.&lt;/p&gt;




&lt;h2&gt;
  
  
  Access and Configure Umami
&lt;/h2&gt;

&lt;p&gt;After deployment, access the Umami dashboard using your configured domain, log in with the default administrator credentials, and rotate the password to secure the instance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Open the login page:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Replace &lt;code&gt;umami.example.com&lt;/code&gt; with your configured domain and open &lt;code&gt;https://umami.example.com/login&lt;/code&gt; in a web browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Log in using the default administrator credentials.&lt;/strong&gt; The username is &lt;code&gt;admin&lt;/code&gt; and the password is &lt;code&gt;umami&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Change the default credentials immediately after the first login to secure your analytics instance.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;3. Change the password:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At the bottom of the left sidebar, click &lt;strong&gt;admin&lt;/strong&gt; and select &lt;strong&gt;Settings&lt;/strong&gt;. Navigate to the &lt;strong&gt;Profile&lt;/strong&gt; tab, click &lt;strong&gt;Change password&lt;/strong&gt;, enter the current password (&lt;code&gt;umami&lt;/code&gt;), enter your new secure password, and click &lt;strong&gt;Save&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Track a Sample Website
&lt;/h2&gt;

&lt;p&gt;Umami tracks visitors through a lightweight JavaScript snippet embedded in your target website. The following steps add a website, retrieve the tracking code, embed it in a sample page, and verify that metrics appear in the dashboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add a Website
&lt;/h3&gt;

&lt;p&gt;Umami tracks each site separately, so a website entry must exist before its tracking code can be generated.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the Umami dashboard, click &lt;strong&gt;Websites&lt;/strong&gt; in the left navigation pane.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add website&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Enter a name such as &lt;code&gt;My Test Site&lt;/code&gt; and a domain such as &lt;code&gt;test.example.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Save&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Retrieve the Tracking Code
&lt;/h3&gt;

&lt;p&gt;Each website gets a unique tracking snippet tied to its website ID.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find the new website in the list and click &lt;strong&gt;Edit&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Navigate to the &lt;strong&gt;Tracking code&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;Copy the generated &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Because &lt;code&gt;TRACKER_SCRIPT_NAME=custom-stats&lt;/code&gt; was set in the environment file, the generated tag uses &lt;code&gt;/custom-stats&lt;/code&gt; as an additional path alongside the default &lt;code&gt;/script.js&lt;/code&gt;. Using the custom path helps bypass ad blockers that specifically target the default filename.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Embed the Tracking Code
&lt;/h3&gt;

&lt;p&gt;The script must load on every page you want Umami to track.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the main HTML document or the global layout component of your sample web application.&lt;/li&gt;
&lt;li&gt;Paste the tracking code into the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; section. Replace &lt;code&gt;YOUR_WEBSITE_ID&lt;/code&gt; with the ID from your Umami dashboard and &lt;code&gt;umami.example.com&lt;/code&gt; with your domain:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;   &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
       &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
       &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Sample Web Application&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
       &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;defer&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://umami.example.com/custom-stats"&lt;/span&gt; &lt;span class="na"&gt;data-website-id=&lt;/span&gt;&lt;span class="s"&gt;"YOUR_WEBSITE_ID"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Save the file and open the sample web application in a browser.&lt;/li&gt;
&lt;li&gt;Refresh the page a few times to simulate visitor traffic.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Verify Analytics Collection
&lt;/h3&gt;

&lt;p&gt;Confirming that traffic appears in the dashboard validates that the tracking script is reaching the server.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Return to the Umami dashboard and select your website.&lt;/li&gt;
&lt;li&gt;Review the main dashboard to verify captured page views, referrers, device types, and visitor data.&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Realtime&lt;/strong&gt; tab to monitor active visitors navigating the sample site.&lt;/li&gt;
&lt;/ol&gt;




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

&lt;p&gt;Umami is running with a privacy-first, self-hosted analytics dashboard tracking live traffic. From here you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add more websites and compare traffic across properties from a single dashboard&lt;/li&gt;
&lt;li&gt;Configure custom events to track conversions, signups, or other in-app actions&lt;/li&gt;
&lt;li&gt;Enable email reports or connect the Umami API to pull metrics into external tooling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the full guide with additional tips, visit the original article on &lt;strong&gt;&lt;a href="https://docs.vultr.com/how-to-deploy-umami-open-source-analytics-platform" rel="noopener noreferrer"&gt;Vultr Docs&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>privacy</category>
      <category>docker</category>
      <category>selfhosted</category>
    </item>
  </channel>
</rss>
