<?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: Ifeoluwa Afolabi (Afoxcute)</title>
    <description>The latest articles on DEV Community by Ifeoluwa Afolabi (Afoxcute) (@ifeoluwa_afolabiafoxcut).</description>
    <link>https://dev.to/ifeoluwa_afolabiafoxcut</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1993660%2F9495a43e-5269-4d8f-9521-710b295f021f.jpg</url>
      <title>DEV Community: Ifeoluwa Afolabi (Afoxcute)</title>
      <link>https://dev.to/ifeoluwa_afolabiafoxcut</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ifeoluwa_afolabiafoxcut"/>
    <language>en</language>
    <item>
      <title>I Self-Hosted SigNoz on AWS EC2 From Scratch — Every Wall I Hit and How I Fixed It</title>
      <dc:creator>Ifeoluwa Afolabi (Afoxcute)</dc:creator>
      <pubDate>Sun, 19 Jul 2026 08:54:14 +0000</pubDate>
      <link>https://dev.to/ifeoluwa_afolabiafoxcut/i-self-hosted-signoz-on-aws-ec2-from-scratch-every-wall-i-hit-and-how-i-fixed-it-5d72</link>
      <guid>https://dev.to/ifeoluwa_afolabiafoxcut/i-self-hosted-signoz-on-aws-ec2-from-scratch-every-wall-i-hit-and-how-i-fixed-it-5d72</guid>
      <description>&lt;p&gt;The WeMakeDevs "Agents of SigNoz" hackathon said: self-host SigNoz, instrument a real app, send data. Simple enough on paper. Two hours and seven distinct failure modes later, I had traces flowing. Here's everything that happened in the order it happened.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I Set Out to Do&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Self-host the full SigNoz stack on a raw AWS EC2 Ubuntu instance using the Linux binary install&lt;/li&gt;
&lt;li&gt;Deploy a real Node.js Express backend with OpenTelemetry auto-instrumentation&lt;/li&gt;
&lt;li&gt;Generate real traffic — GETs, POSTs, 404s, 400s, intentional errors, slow requests&lt;/li&gt;
&lt;li&gt;Watch all 65 requests appear as traces in SigNoz&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The machine:&lt;/strong&gt; Ubuntu 22.04, &lt;code&gt;t3.medium&lt;/code&gt;, &lt;code&gt;ip-172-**-**-***&lt;/code&gt; on AWS EC2.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The stack under the hood:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ClickHouse&lt;/td&gt;
&lt;td&gt;Columnar database storing all traces, logs, and metrics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ZooKeeper&lt;/td&gt;
&lt;td&gt;Coordination layer ClickHouse needs for distributed operation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ClickHouse Keeper&lt;/td&gt;
&lt;td&gt;ZooKeeper-compatible alternative built into ClickHouse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SigNoz&lt;/td&gt;
&lt;td&gt;The observability application layer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SigNoz OTel Collector&lt;/td&gt;
&lt;td&gt;The OpenTelemetry ingestion pipeline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node.js 20 + Express&lt;/td&gt;
&lt;td&gt;The demo backend being observed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Part 1: Installing the Stack
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Wall 1 — ZooKeeper Download Returned 196 Bytes
&lt;/h3&gt;

&lt;p&gt;The SigNoz docs say to run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash
curl &lt;span class="nt"&gt;-L&lt;/span&gt; https://dlcdn.apache.org/zookeeper/zookeeper-3.8.5/apache-zookeeper-3.8.5-bin.tar.gz &lt;span class="nt"&gt;-o&lt;/span&gt; zookeeper.tar.gz
&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xzf&lt;/span&gt; zookeeper.tar.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What I got:&lt;br&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;gzip: stdin: not in gzip format
tar: Child returned status 1
tar: Error is not recoverable: exiting now
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Apache CDN returned an HTML redirect page of 196 bytes instead of the binary. Version 3.8.5 had moved off the active mirror. Fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget https://archive.apache.org/dist/zookeeper/zookeeper-3.8.5/apache-zookeeper-3.8.5-bin.tar.gz &lt;span class="nt"&gt;-O&lt;/span&gt; zookeeper.tar.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Always verify before extracting:&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;# Must say: gzip compressed data and NOT "HTML document"&lt;/span&gt;
file zookeeper.tar.gz

&lt;span class="c"&gt;# Must be ~12-15MB and NOT 196 bytes&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-lh&lt;/span&gt; zookeeper.tar.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Wall 2 — Permission Errors Setting Up ZooKeeper
&lt;/h3&gt;

&lt;p&gt;Running the manual setup commands without &lt;code&gt;sudo&lt;/code&gt; caused a cascade of permission failures:&lt;br&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;cp: cannot create regular file '/opt/zookeeper/conf/zoo.cfg': Permission denied
sed: can't read /opt/zookeeper/conf/zoo.cfg: No such file or directory
FAILED TO WRITE PID
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The correct sequence is using &lt;code&gt;sudo bash -c&lt;/code&gt; to write configs and creating the ZooKeeper user before handing over ownership:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /opt/zookeeper /var/lib/zookeeper /var/log/zookeeper
&lt;span class="nb"&gt;sudo cp&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; apache-zookeeper-3.8.5-bin/&lt;span class="k"&gt;*&lt;/span&gt; /opt/zookeeper

&lt;span class="nb"&gt;sudo &lt;/span&gt;bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'cat &amp;lt;&amp;lt;EOF &amp;gt; /opt/zookeeper/conf/zoo.cfg
tickTime=2000
dataDir=/var/lib/zookeeper
clientPort=2181
admin.serverPort=3181
EOF'&lt;/span&gt;

&lt;span class="nb"&gt;sudo &lt;/span&gt;getent passwd zookeeper &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;sudo &lt;/span&gt;useradd &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--home&lt;/span&gt; /opt/zookeeper &lt;span class="nt"&gt;--no-create-home&lt;/span&gt; &lt;span class="nt"&gt;--user-group&lt;/span&gt; &lt;span class="nt"&gt;--shell&lt;/span&gt; /sbin/nologin zookeeper

&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; zookeeper:zookeeper /opt/zookeeper
&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; zookeeper:zookeeper /var/lib/zookeeper
&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; zookeeper:zookeeper /var/log/zookeeper
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Wall 3 — ClickHouse Was Running But Not via systemd
&lt;/h3&gt;

&lt;p&gt;After installing ClickHouse, the service had been started manually rather than through systemd. This caused:&lt;br&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;Failed to start clickhouse-server.service: Unit clickhouse-server.service not found.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But &lt;code&gt;ps aux | grep clickhouse&lt;/code&gt; showed it was actually running (PID 2338). The systemd unit file either didn't exist or pointed to the wrong binary path (&lt;code&gt;status=203/EXEC&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The fix was creating a proper service file pointing to the correct binary and creating the missing runtime directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /run/clickhouse-server
&lt;span class="nb"&gt;sudo chown &lt;/span&gt;clickhouse:clickhouse /run/clickhouse-server
&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; clickhouse:clickhouse /var/lib/clickhouse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create the systemd unit file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'cat &amp;lt;&amp;lt;EOF &amp;gt; /etc/systemd/system/clickhouse-server.service
[Unit]
Description=ClickHouse Server
After=network-online.target clickhouse-keeper.service

[Service]
Type=simple
User=clickhouse
Group=clickhouse
Restart=on-failure
RuntimeDirectory=clickhouse-server
ExecStart=/usr/bin/clickhouse server \
  --config=/etc/clickhouse-server/config.xml \
  --pid-file=/run/clickhouse-server/clickhouse-server.pid
LimitNOFILE=500000
LimitNPROC=500000
LimitCORE=infinity
TasksMax=infinity
OOMScoreAdjust=-1000

[Install]
WantedBy=multi-user.target
EOF'&lt;/span&gt;

&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl daemon-reload
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start clickhouse-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Wall 4 — Multiple Authentication Methods Conflict
&lt;/h3&gt;

&lt;p&gt;Setting the ClickHouse password should be simple, create a file in &lt;code&gt;users.d/&lt;/code&gt; with a SHA256 hash. But ClickHouse 26.x is strict: if &lt;code&gt;users.xml&lt;/code&gt; already defines an auth method (even an empty password), and your &lt;code&gt;users.d/&lt;/code&gt; file adds another, it refuses to start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cannot specify multiple authentication methods for user default.
Specify only one authentication method.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix is to explicitly remove the existing password before adding the new one using the &lt;code&gt;remove="1"&lt;/code&gt; attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'cat &amp;lt;&amp;lt;EOF &amp;gt; /etc/clickhouse-server/users.d/default-password.xml
&amp;lt;clickhouse&amp;gt;
    &amp;lt;users&amp;gt;
        &amp;lt;default&amp;gt;
            &amp;lt;password remove="1"/&amp;gt;
            &amp;lt;password_sha256_hex&amp;gt;5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8&amp;lt;/password_sha256_hex&amp;gt;
        &amp;lt;/default&amp;gt;
    &amp;lt;/users&amp;gt;
&amp;lt;/clickhouse&amp;gt;
EOF'&lt;/span&gt;

&lt;span class="nb"&gt;sudo chown &lt;/span&gt;clickhouse:clickhouse /etc/clickhouse-server/users.d/default-password.xml
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;640 /etc/clickhouse-server/users.d/default-password.xml
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart clickhouse-server
&lt;span class="nb"&gt;sleep &lt;/span&gt;5

&lt;span class="c"&gt;# Confirm it works&lt;/span&gt;
clickhouse-client &lt;span class="nt"&gt;--password&lt;/span&gt; password &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"SELECT 1"&lt;/span&gt;
&lt;span class="c"&gt;# Expected output: 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hash &lt;code&gt;5e884898...&lt;/code&gt; is the SHA256 of the string &lt;code&gt;password&lt;/code&gt;. Verify it yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"password"&lt;/span&gt; | &lt;span class="nb"&gt;sha256sum&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; The password appears in four places. All four must match or something will silently fail:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;ClickHouse &lt;code&gt;users.d/default-password.xml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Migration DSN: &lt;code&gt;tcp://localhost:9000?password=password&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;SigNoz &lt;code&gt;systemd.env&lt;/code&gt;: &lt;code&gt;SIGNOZ_TELEMETRYSTORE_CLICKHOUSE_DSN=tcp://localhost:9000?password=password&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;OTel Collector &lt;code&gt;config.yaml&lt;/code&gt; and all four exporter DSNs&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Wall 5 — ClickHouse Keeper vs ZooKeeper
&lt;/h3&gt;

&lt;p&gt;Here's something the docs gloss over: ClickHouse ships with ClickHouse Keeper which is a ZooKeeper-compatible replacement built in. It was already running on my machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight systemd"&gt;&lt;code&gt;&lt;span class="err"&gt;●&lt;/span&gt; &lt;span class="err"&gt;clickhouse-keeper.service&lt;/span&gt;
   &lt;span class="err"&gt;Active:&lt;/span&gt; &lt;span class="err"&gt;active&lt;/span&gt; &lt;span class="err"&gt;(running)&lt;/span&gt; &lt;span class="err"&gt;since&lt;/span&gt; &lt;span class="err"&gt;Sun&lt;/span&gt; &lt;span class="err"&gt;2026-07-19&lt;/span&gt; &lt;span class="err"&gt;04:10&lt;/span&gt;
   &lt;span class="err"&gt;Main&lt;/span&gt; &lt;span class="err"&gt;PID:&lt;/span&gt; &lt;span class="err"&gt;3295&lt;/span&gt; &lt;span class="err"&gt;(/usr/bin/clickhouse-keeper)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a single node, you don't need both. ClickHouse Keeper uses the same port &lt;code&gt;2181&lt;/code&gt; as ZooKeeper, so the &lt;code&gt;cluster.xml&lt;/code&gt; config works with either. This is one less process to install and debug.&lt;/p&gt;




&lt;h3&gt;
  
  
  What the Migration Commands Actually Do
&lt;/h3&gt;

&lt;p&gt;Once ClickHouse was running and the password was correct, the three migration commands ran clean:&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;ARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/x86_64/amd64/g'&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/aarch64/arm64/g'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Step 1 — Creates the databases&lt;/span&gt;
./signoz-otel-collector_linux_&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ARCH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/bin/signoz-otel-collector migrate bootstrap &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--clickhouse-dsn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"tcp://localhost:9000?password=password"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--clickhouse-replication&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;

&lt;span class="c"&gt;# Step 2 — Creates tables and indexes (synchronous, wait for prompt)&lt;/span&gt;
./signoz-otel-collector_linux_&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ARCH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/bin/signoz-otel-collector migrate &lt;span class="nb"&gt;sync &lt;/span&gt;up &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--clickhouse-dsn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"tcp://localhost:9000?password=password"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--clickhouse-replication&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;

&lt;span class="c"&gt;# Step 3 — Runs background data migrations (async, takes longest)&lt;/span&gt;
./signoz-otel-collector_linux_&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ARCH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/bin/signoz-otel-collector migrate async up &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--clickhouse-dsn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"tcp://localhost:9000?password=password"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--clickhouse-replication&lt;/span&gt;&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;Run each one and wait for the &lt;code&gt;$&lt;/code&gt; prompt to return before running the next. Migrations are successful when you see only &lt;code&gt;info&lt;/code&gt; level log lines with no &lt;code&gt;error&lt;/code&gt; lines.&lt;/p&gt;




&lt;h3&gt;
  
  
  SigNoz Health Check: Green
&lt;/h3&gt;

&lt;p&gt;After migrations, installing SigNoz and the OTel Collector, and starting all services:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET http://localhost:8080/api/v1/health
&lt;span class="c"&gt;# Expected: {"status":"ok"}&lt;/span&gt;
&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%2Fsirmf9uo6pvu932d798s.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%2Fsirmf9uo6pvu932d798s.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&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%2F0geecdqoo5i284stx730.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%2F0geecdqoo5i284stx730.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&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%2F4s96g5mpdz54yk74m81p.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%2F4s96g5mpdz54yk74m81p.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Screenshot 1 — Terminal showing the health check response and all three systemd services active and running&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Part 2: Wiring a Real Backend to SigNoz
&lt;/h2&gt;

&lt;p&gt;Installing SigNoz is one thing. Sending real telemetry to it is where it gets interesting.&lt;/p&gt;
&lt;h3&gt;
  
  
  The App
&lt;/h3&gt;

&lt;p&gt;A Node.js Express backend with routes that deliberately generate different kinds of traffic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET  /health           → health check (instant)
GET  /api/users        → returns all users (50ms delay)
GET  /api/users/:id    → single user, 404 if not found
POST /api/users        → create user, 400 if fields missing
GET  /api/orders       → returns all orders (100ms delay)
POST /api/orders       → create order
GET  /api/error        → intentional 500 error
GET  /api/slow?ms=N    → artificial delay, tests latency traces
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The slow and error routes exist specifically to generate interesting data in SigNoz latency distributions and error traces are only useful if you have something to look at.&lt;/p&gt;




&lt;h3&gt;
  
  
  OpenTelemetry Instrumentation
&lt;/h3&gt;

&lt;p&gt;The key file is &lt;code&gt;tracing.js&lt;/code&gt;, loaded before everything else via &lt;code&gt;--require&lt;/code&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NodeSDK&lt;/span&gt; &lt;span class="p"&gt;}&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;@opentelemetry/sdk-node&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getNodeAutoInstrumentations&lt;/span&gt; &lt;span class="p"&gt;}&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;@opentelemetry/auto-instrumentations-node&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;OTLPTraceExporter&lt;/span&gt; &lt;span class="p"&gt;}&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;@opentelemetry/exporter-trace-otlp-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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;OTLPMetricExporter&lt;/span&gt; &lt;span class="p"&gt;}&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;@opentelemetry/exporter-metrics-otlp-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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PeriodicExportingMetricReader&lt;/span&gt; &lt;span class="p"&gt;}&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;@opentelemetry/sdk-metrics&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;OTLP_ENDPOINT&lt;/span&gt; &lt;span class="o"&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;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SIGNOZ_HOST&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:4318`&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;sdk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NodeSDK&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;traceExporter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OTLPTraceExporter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;url&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;OTLP_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/v1/traces`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;metricReader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PeriodicExportingMetricReader&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;exporter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OTLPMetricExporter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;url&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;OTLP_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/v1/metrics`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;exportIntervalMillis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;instrumentations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;getNodeAutoInstrumentations&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The start script in &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node --require ./tracing.js server.js"&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;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero changes to &lt;code&gt;server.js&lt;/code&gt; OpenTelemetry instruments Express, HTTP, and all outgoing requests automatically. The &lt;code&gt;--require&lt;/code&gt; flag guarantees tracing initializes before any other module loads. If you import it inside &lt;code&gt;server.js&lt;/code&gt; instead, you may miss early spans.&lt;/p&gt;




&lt;h3&gt;
  
  
  Running as a systemd Service
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'cat &amp;lt;&amp;lt;EOF &amp;gt; /etc/systemd/system/demo-backend.service
[Unit]
Description=Demo Backend App
After=signoz-otel-collector.service

[Service]
User=ubuntu
WorkingDirectory=/home/ubuntu/demo-backend
EnvironmentFile=/home/ubuntu/demo-backend/.env
ExecStart=/usr/bin/node --require ./tracing.js server.js
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF'&lt;/span&gt;

&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl daemon-reload
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start demo-backend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Part 3: Generating 65 Real Requests
&lt;/h2&gt;

&lt;p&gt;A load test script ran 5 rounds hitting every route healthy requests, 404s, 400s, intentional errors, and slow responses:&lt;br&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;🔄 Round 5/5
[GET]  /health           → 200
[GET]  /api/users        → 200
[GET]  /api/users/1      → 200
[GET]  /api/users/2      → 200
[GET]  /api/users/999    → 404
[POST] /api/users        → 201
[POST] /api/users        → 400
[GET]  /api/orders       → 200
[GET]  /api/orders/1     → 200
[GET]  /api/orders/999   → 404
[POST] /api/orders       → 201
[GET]  /api/error        → 500
[GET]  /api/slow?ms=1500 → 200

──────────────────────────────────────
✅ Load test completed!
📊 Total requests : 65
✅ Successful     : 45
❌ Errors         : 20
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;20 out of 65 requests are errors which is a mix of 404s, 400s, and intentional 500s. This is intentional: SigNoz's error rate and latency charts are only meaningful when you have both healthy and unhealthy traffic flowing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 4: What SigNoz Showed
&lt;/h2&gt;

&lt;p&gt;Of everything I saw, the Traces Explorer is the feature I'd tell someone about first. Before this, debugging a 500 error meant checking server logs, adding more logging, redeploying, reproducing the error, checking again. With the Traces Explorer, I clicked the 500 trace from /api/error, and the full span tree opened. I saw request in, handler hit, exception thrown, response out with the exact error message sitting in the attributes panel. No log file. No grep. No redeploy. The entire sequence of what happened was just there. That single click changed how I think about debugging.&lt;/p&gt;

&lt;h3&gt;
  
  
  Services View
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;demo-backend&lt;/code&gt; appears with request rate, error rate, and p99 latency. The &lt;code&gt;/api/slow&lt;/code&gt; route pushed p99 latency above 1500ms, immediately visible as a red flag on the service overview.&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%2Fpfk7wcciso40620qasly.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%2Fpfk7wcciso40620qasly.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Screenshot 2 — SigNoz Services page showing demo-backend with p99 latency, error rate, and RPS&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Traces Explorer
&lt;/h3&gt;

&lt;p&gt;Every one of the 65 requests appears as an individual trace. Each trace shows the full span: incoming HTTP request → Express route handler → response time.&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%2Fux53kn7p12svfno33m2w.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%2Fux53kn7p12svfno33m2w.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Screenshot 3 — Traces Explorer filtered to demo-backend showing a mix of 200 and 500/404 status codes&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Error Trace Lists
&lt;/h3&gt;

&lt;p&gt;Clicking on one of the 500 trace from &lt;code&gt;/api/error&lt;/code&gt; will show the exception message inline with the full span tree.&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%2Feyugeq82qoi2qfpaueot.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%2Feyugeq82qoi2qfpaueot.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Screenshot 4 — All 500 traces for /api/error showing the errors span in red&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Latency Distribution
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;/api/slow?ms=1500&lt;/code&gt; traces cluster visibly at the 1.5 second mark in the latency histogram, completely separate from the healthy sub-100ms requests.&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%2Fdxkcbvtuil0ei2odnkmm.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%2Fdxkcbvtuil0ei2odnkmm.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Screenshot 5 — Latency histogram for demo-backend showing the p99 spike from the slow endpoint&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Query Builder
&lt;/h3&gt;

&lt;p&gt;Filtering by &lt;code&gt;service.name = demo-backend&lt;/code&gt; and &lt;code&gt;http.status_code = 500&lt;/code&gt; returns exactly the 5 error traces, one per round:&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%2F2xowffou1va6li5ze8c1.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%2F2xowffou1va6li5ze8c1.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Screenshot 6 — Query Builder filtered to demo-backend and status 500 returning exactly 5 error traces&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Alert Setup
&lt;/h3&gt;

&lt;p&gt;With real error data flowing, I set up an alert in under 3 minutes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Alerts → New Alert → Trace-based Alert&lt;/li&gt;
&lt;li&gt;Query: &lt;code&gt;service.name = 'demo-backend'&lt;/code&gt; with &lt;code&gt;count()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Condition: &lt;code&gt;ABOVE&lt;/code&gt; threshold, &lt;code&gt;AT LEAST ONCE&lt;/code&gt;, during &lt;code&gt;Last 5 minutes&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Severity: &lt;code&gt;critical&lt;/code&gt;, value &lt;code&gt;&amp;gt; 0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Notification message: &lt;code&gt;High error rate detected on demo-backend. Error count exceeded threshold. Check /api/error traces in SigNoz.&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&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%2Fsomnykktburpd7hvtp1o.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%2Fsomnykktburpd7hvtp1o.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&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%2F3jcb4nubb5pkebmju9gu.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%2F3jcb4nubb5pkebmju9gu.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&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%2F7rpwjlag2zf1mwbrwpfs.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%2F7rpwjlag2zf1mwbrwpfs.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Screenshot 7 — Alert rule page showing the configured trace-based alert with query, threshold, and notification message&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd Tell You Before You Start
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Set your ClickHouse password before anything else.&lt;/strong&gt; It appears in four places: the ClickHouse user config, the SigNoz systemd env file, the OTel Collector config, and the migration DSN. All four must match. Decide your password before you install anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;&amp;lt;password remove="1"/&amp;gt;&lt;/code&gt; when setting ClickHouse passwords via config file.&lt;/strong&gt; Without it, ClickHouse 26.x will crash on startup complaining about multiple auth methods, even if the existing password is empty.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use ClickHouse Keeper instead of standalone ZooKeeper.&lt;/strong&gt; It's included with ClickHouse, uses the same protocol and port, and is one less process to install and debug.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verify every download before extracting.&lt;/strong&gt; &lt;code&gt;file yourfile.tar.gz&lt;/code&gt; takes two seconds and saves you a confusing error that looks like a tar bug but is actually an HTML redirect page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create the runtime directory before starting ClickHouse.&lt;/strong&gt; &lt;code&gt;/run/clickhouse-server/&lt;/code&gt; must exist and be owned by the &lt;code&gt;clickhouse&lt;/code&gt; user or the server won't start and the error message (&lt;code&gt;CANNOT_OPEN_FILE&lt;/code&gt; for the PID file) doesn't make this obvious.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open your EC2 security group ports early.&lt;/strong&gt; You need: &lt;code&gt;8080&lt;/code&gt; (SigNoz UI), &lt;code&gt;4317&lt;/code&gt;/&lt;code&gt;4318&lt;/code&gt; (OTLP), &lt;code&gt;9000&lt;/code&gt; (ClickHouse), &lt;code&gt;2181&lt;/code&gt; (Keeper/ZooKeeper), &lt;code&gt;5000&lt;/code&gt; (your app). I spent 20 minutes debugging a connectivity issue that was just a missing inbound rule.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Load &lt;code&gt;tracing.js&lt;/code&gt; with &lt;code&gt;--require&lt;/code&gt;, not &lt;code&gt;import&lt;/code&gt;.&lt;/strong&gt; OpenTelemetry must initialize before any other module loads. &lt;code&gt;node --require ./tracing.js server.js&lt;/code&gt; guarantees this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run each migration command and wait for the &lt;code&gt;$&lt;/code&gt; prompt before running the next one.&lt;/strong&gt; The async migration in particular can take 1–2 minutes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Self-hosting SigNoz on Linux is worth doing if you want to understand what observability infrastructure actually is and not just what it looks like behind a cloud dashboard. Running 65 real requests through a real backend and watching every one appear as a searchable, filterable trace, with latency distributions and error details attached, makes the value of OpenTelemetry concrete in a way that reading docs never does.&lt;/p&gt;

&lt;p&gt;The walls I hit stale mirror URLs, permission errors, password conflicts, missing runtime directories, wrong binary paths were all fixable. None of them were fundamental flaws in SigNoz. They were the kind of friction that only shows up when you go off the happy path of Docker Compose and actually install a distributed system from scratch.&lt;/p&gt;

&lt;p&gt;If you're joining the Agents of SigNoz hackathon and want a foundation you understand from the ground up, the binary install is the way to get there.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Self-hosted on AWS EC2 Ubuntu 22.04 · ClickHouse 26.6.1 · SigNoz OTel Collector latest · Node.js 20 · July 2026&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Part of the WeMakeDevs "Agents of SigNoz" hackathon — &lt;a href="https://signoz.io/" rel="noopener noreferrer"&gt;signoz.io&lt;/a&gt; · &lt;a href="https://wemakedevs.org/" rel="noopener noreferrer"&gt;wemakedevs.org&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>observability</category>
      <category>opentelemetry</category>
      <category>signoz</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
