How to Get Real Client IPs in Express Behind Cloudflare and Nginx (Without IP Spoofing)

If you are running an Express.js application behind a reverse proxy setup like Cloudflare → Nginx → Express, accurately identifying a visitor’s real IP address is critical. Whether you are enforcing rate limits, analyzing web traffic, or logging security events, relying on low-level socket properties like req.socket.remoteAddress will leave you seeing only Cloudflare’s or Nginx’s IP address.

Worse, misconfiguring proxy trust settings in Express can expose your application to IP spoofing attacks, allowing malicious actors to bypass rate limiters with forged headers.

Here is a breakdown of how the header chain works, why traditional setups fail, and how to safely configure Express with app.set('trust proxy', 2).


The Problem: Reverse Proxies and Header Chains

When a user visits your web application through Cloudflare and Nginx, the request travels through two distinct proxy hops before hitting your Node.js application:

  1. Client connects to Cloudflare (162.159.x.x)
  2. Cloudflare connects to your Nginx server (127.0.0.1)
  3. Nginx forwards the request to Express.js

If you query req.socket.remoteAddress inside Express, Node.js will report 127.0.0.1 (or Nginx’s local socket IP). If you do not evaluate proxy headers, your rate limiter will treat every user on the internet as a single visitor, quickly locking out legitimate traffic.

To pass the real client IP down the chain, proxies append client IP addresses to the X-Forwarded-For HTTP header.


Understanding the X-Forwarded-For Header

As a request moves through each proxy, the X-Forwarded-For header gets appended from left to right:

X-Forwarded-For: Client IP, Cloudflare IP

When Nginx receives the request from Cloudflare and passes it to Express via proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;, the full header reaching Express looks like this:

X-Forwarded-For: 203.0.113.195, 162.159.116.147

  • 203.0.113.195 is the real client IP attached by Cloudflare.
  • 162.159.116.147 is the Cloudflare edge proxy IP attached by Nginx.

The Vulnerability: Why app.set('trust proxy', true) Is Dangerous

A common solution in Express tutorials is to enable full proxy trust:

// DO NOT DO THIS IN PRODUCTION
app.set('trust proxy', true);

Setting trust proxy to true tells Express to trust every IP listed in the X-Forwarded-For chain without restriction, returning the leftmost IP as req.ip.

How Attackers Exploit This

If an attacker sends a request directly to your origin or through Cloudflare with a manually forged header:

X-Forwarded-For: 1.1.1.1, 2.2.2.2

  1. Cloudflare appends the attacker’s actual IP (198.51.100.5): X-Forwarded-For: 1.1.1.1, 2.2.2.2, 198.51.100.5
  2. Nginx appends Cloudflare’s IP (162.159.116.147): X-Forwarded-For: 1.1.1.1, 2.2.2.2, 198.51.100.5, 162.159.116.147
  3. With trust proxy: true, Express steps all the way back to the leftmost IP and resolves req.ip as 1.1.1.1.

By changing the fake IP in their request header on every hit, an attacker can trivially bypass custom rate limiters and IP-based blocking logic.


The Solution: Explicit Hop Counting with trust proxy: 2

Instead of trusting the entire header blindly, you can tell Express exactly how many proxy hops sit between it and the untrusted public internet.

In a Cloudflare → Nginx → Express stack, there are exactly 2 trusted hops:

  1. Hop 1 (Outer): Cloudflare
  2. Hop 2 (Inner): Nginx

To configure Express safely, explicitly set:

// Trust exactly 2 proxy hops back from Express
app.set('trust proxy', 2);

How Express Resolves req.ip with Hop Counting

When trust proxy is set to 2, Express evaluates the X-Forwarded-For chain starting from the right (the closest proxy) and steps back exactly 2 hops:

X-Forwarded-For: ["1.1.1.1", "2.2.2.2", "198.51.100.5", "162.159.116.147"]
                                         ▲               ▲
                                         │               └─ Hop 1 (Nginx)
                                         └─ Hop 2 (Cloudflare) -> Express selects THIS as req.ip

Express ignores 1.1.1.1 and 2.2.2.2 entirely because they lie outside the trusted 2-hop boundary. req.ip correctly resolves to 198.51.100.5 (the real client IP validated by Cloudflare).


Verifying Your Setup

You can verify how Express processes incoming IP chains by adding a temporary debugging middleware:

app.use((req, res, next) => {
  const rawXff = req.headers['x-forwarded-for'] || '';
  const xffArray = rawXff.split(',').map(ip => ip.trim());

  console.log('--- IP Resolution Debug ---');
  console.log(`Raw X-Forwarded-For Header : "${rawXff}"`);
  console.log(`Parsed IP Array            :`, xffArray);
  console.log(`Express Selected req.ip    : ${req.ip}`);
  console.log(`Direct Socket Remote IP    : ${req.socket.remoteAddress}`);
  console.log('---------------------------');

  next();
});

Key Rules to Remember

  • Always read req.ip in custom rate limiters or security tools—never read req.socket.remoteAddress directly when running behind a proxy.
  • Avoid app.set('trust proxy', true) in multi-tier architecture to prevent header injection vulnerabilities.
  • Match your trust proxy integer setting directly to the number of reverse proxy layers in your infrastructure (e.g., 1 for Nginx only, 2 for Cloudflare + Nginx).

As I was testing this setup I found that even though Express was logging the real client IP, NGINX was not. It turns out you also have to add following to the http block in /etc/nginx/nginx.conf for NGINX itself to log the real client IP in its logs:

http {

    # https://gemini.google.com/app/2722d33b9f3f1f27
    # Trust Cloudflare IPv4 ranges
    set_real_ip_from 173.245.48.0/20;
    set_real_ip_from 103.21.244.0/22;
    set_real_ip_from 103.22.200.0/22;
    set_real_ip_from 103.31.4.0/22;
    set_real_ip_from 104.16.0.0/13;
    set_real_ip_from 104.24.0.0/14;
    set_real_ip_from 108.162.192.0/18;
    set_real_ip_from 131.0.72.0/22;
    set_real_ip_from 141.101.64.0/18;
    set_real_ip_from 162.158.0.0/15;
    set_real_ip_from 172.64.0.0/13;
    set_real_ip_from 188.114.96.0/20;
    set_real_ip_from 190.93.240.0/20;
    set_real_ip_from 197.234.240.0/22;
    set_real_ip_from 198.41.128.0/17;

    # Trust Cloudflare IPv6 ranges
    set_real_ip_from 2400:cb00::/32;
    set_real_ip_from 2606:4700::/32;
    set_real_ip_from 2803:f800::/32;
    set_real_ip_from 2405:b500::/32;
    set_real_ip_from 2405:8100::/32;
    set_real_ip_from 2a06:98c0::/29;
    set_real_ip_from 2c0f:f248::/32;

    # Tell Nginx which header contains the real visitor IP
    real_ip_header CF-Connecting-IP;
}

Add this to /etc/nginx/nginx.conf so its available to all other configs without having to add to each one of them.

Posted in Computers, programming, Software | Tagged , | Leave a comment

Google Ads Tips

First of all I don’t recommend it. Its very difficult to setup and even after spending thousands of dollars I have yet to see any ROI – return on investment. It does all kinds of deceptive and misleading things. Still if you are compelled to use it, here are some tips to avoid shooting yourself in the foot:

  1. Do not use broad search or it will display your ad on completely irrelevant queries. Always use phrase match or exact match.
  2. If you are advertising a paid service (most likely) add free as a negative keyword
  3. Turn off display network or it will keep showing your ad on irrelevant pages
  4. If targetting a location make sure to unselect Presence or Interest. Only select Presence and even then it might not target the location and instead target all countries and territories
  5. Do not start with maximize conversions on a fresh campaign. Use Maximize clicks (even manual CPC is fine). Once you have 30+ conversions only then switch to maximize conversions so it knows how it can maximize. When you turn on maximize conversions on a fresh campaign with no conversion data it has no idea what clicks will lead to a conversion.

Understand the difference between search ads and display ads. From https://instapage.com/blog/search-ads-vs-display-ads:

  • The average Google Ads CPC is $2.41 for search network ads, and only $0.59 for display network ads
  • The average conversion rate across all industries is 4.40% for search campaigns, while the average for display campaigns is only 0.57%
  • search ads are a form of “pull” advertising while display ads are “push” advertising

Let me know in comments what you think.

Posted in Computers | Leave a comment

Cold Email Marketing

I am running a cold email outreach campaign. Does the daily volume of emails I send influence risk of my account getting flagged as spam?

Yes, the daily volume of cold emails you send significantly influences the risk of your account or domain getting flagged as spam. However, it’s not the only factor—sender behavior, reputation, and other practices play a bigger role in long-term deliverability.

Why Volume Matters

Email providers (Gmail, Outlook, Yahoo, etc.) and spam filters monitor patterns that distinguish legitimate senders from spammers:

  • Sudden spikes or high volumes mimic bulk/spammy behavior and can trigger filters, lower your sender reputation, and push emails to spam folders or cause blocks.
  • Consistency and gradual increases are safer. Abrupt jumps (e.g., from 10 to 200 emails/day) raise red flags more than steady, moderate volumes.
  • High volume amplifies other issues: poor engagement (low opens/replies), high bounce rates, or spam complaints quickly damage reputation. Even a few complaints can hurt your domain broadly.

Official ESP limits (e.g., Gmail ~2,000/day for Workspace, Outlook ~1,000) are for general use and far too high for cold outreach. Exceeding safe cold-email thresholds often leads to flags even if you’re under official caps.

Recommended Safe Daily Limits for Cold Emails (2026 Guidance)

These vary by domain age, warmup status, and setup, but common benchmarks include:

  • New/warming domains: Start at 5–20 emails per day per inbox and ramp up gradually (e.g., 10–25% or ~100% max increase per day/week).
  • Warmed/established inboxes: 30–100 cold emails per day per inbox is a common safe range. Some sources suggest capping at ~25–50 for Gmail to minimize risk.
  • Scaling higher: Use multiple inboxes/domains (e.g., 3–5+), dedicated warmup tools, and rotate sending to hit 500–2,000+/day total without burning one account.

Quality > Quantity: Well-personalized emails with good engagement outperform high-volume blasts. Generic or poorly targeted sends hurt more than raw numbers.

Other Key Factors That Influence Spam Risk

Volume interacts with these—high volume makes problems worse:

  • Account warmup: Essential for new domains/inboxes. Gradually build reputation with simulated positive interactions.
  • Authentication: Proper SPF, DKIM, DMARC setup is non-negotiable.
  • Content & compliance: Avoid spam triggers, include unsubscribe links, personalize, and follow CAN-SPAM/GDPR/etc.
  • Engagement & reputation: High reply/open rates help; complaints/bounces hurt.
  • Tools & infrastructure: Cold email platforms with warmup and monitoring are better than raw Gmail/Outlook for volume. Many marketing tools (Mailchimp, SendGrid) prohibit or heavily restrict true cold emails in their terms.

Best Practices to Minimize Risk

  • Warm up properly and ramp volume slowly.
  • Monitor metrics (deliverability, complaints, bounces) with tools.
  • Use dedicated domains for outreach (separate from your main business email).
  • Send at consistent times/cadences.
  • Focus on high-quality, targeted lists and valuable content.

In short, yes—pushing high daily volumes without proper setup greatly increases spam risk, but smart, gradual scaling with strong fundamentals can keep it manageable. Test small, monitor closely, and prioritize reputation over speed. If you’re scaling aggressively, consider professional cold email infrastructure.

Posted in Computers | Leave a comment

Preventing Google Ads from showings ads in locations that you don’t want to target

First of all I don’t recommend Google Ads – it kept showing my ads to people in Nigeria when I had selected US as the location – but if you do, you have to be extra careful to make sure it does not cheat you this way. Some things to check:

in Location Options (closed by default) make sure you select Presence not the default Presence or Interest.

Second if you are using AI Max, there is a further screen where you should only select the location of interest

Posted in Computers | Leave a comment

Q&A with Gemini: Why Developers Hate ServiceNow (And Why CIOs Spend Millions On It Anyway)

If you’ve ever worked inside a large enterprise or a corporate IT department, you’ve likely encountered ServiceNow or similar heavy enterprise ITSM (IT Service Management) systems.

To most engineers and developers used to clean abstractions, fast tools, and low-friction issue tracking, ServiceNow feels like a slow, bloated monster designed to force you to fill out 40 fields on a form just to request a server restart.

Below is an unedited Q&A exploring why ITSM tools feel like they add complexity rather than removing it, what actually powers these multi-billion-dollar platforms behind the scenes, and the corporate dynamics that drive their massive adoption.


Q: Who are ServiceNow’s main competitors?

Because ServiceNow spans multiple operational areas—including IT Service Management (ITSM), Customer Service Management (CSM), HR Delivery, Strategic Portfolio Management (SPM), and low-code digital workflows—its primary competitors vary by product module:

1. Core IT Service Management (ITSM) & Operations (ITOM)

  • Atlassian (Jira Service Management): The fastest-growing mid-market and enterprise alternative, popular with engineering and DevOps-centric teams due to tight Jira integration.
  • BMC Software (BMC Helix): Historically ServiceNow’s primary legacy competitor in heavy enterprise environments, offering hybrid on-prem and cloud setups.
  • Freshworks (Freshservice): A cloud-native alternative targeting mid-sized businesses and enterprises seeking lower total cost of ownership (TCO) and faster deployment times.
  • Ivanti (Ivanti Neurons): Strong in enterprise endpoint management, unified IT asset management (ITAM), and automated patch management combined with ITSM.
  • ManageEngine (ServiceDesk Plus): A cost-effective solution commonly favored by SMBs and mid-market IT teams.

2. Customer Service Management (CSM)

  • Salesforce (Service Cloud / Agentforce): ServiceNow’s main enterprise rival in non-IT workflows, dominating front-office customer service and contact centers.
  • Zendesk: Widely used for consumer and SMB customer support teams looking for scalable, out-of-the-box ticketing and omnichannel support.

3. HR Service Delivery (HRSD) & Enterprise Workflows

  • Workday: Competes directly for HR service delivery, employee onboarding, and internal workforce request routing.
  • SAP & Oracle: Major enterprise resource planning (ERP) suites offering native internal service request modules.
  • Microsoft (Power Platform / System Center): Fills workflow automation gaps through Power Automate, Power Apps, and Teams integrations.

4. Strategic Portfolio Management (SPM)

  • Planview & Broadcom (Clarity): Heavyweight enterprise competitors for project portfolio management, resource allocation, and strategy execution.

Q: I fail to understand what makes ITSM a big thing—it’s an issue tracker after all with hooks and automations. Having used JIRA (which already feels bloated and complicated), ServiceNow feels like it takes it one step further and adds complexity instead of removing it. Is that accurate?

That perspective is actually far from unpopular—it is the dominant sentiment among software engineers, developers, and builders who interact with these platforms daily.

To an engineer used to clean abstractions, modern APIs, and lightweight tools like GitHub Issues or Linear, enterprise ITSM looks like administrative bloat. Here is why it feels that way:

  1. It’s Built for Governance, Not Velocity: Developer tools are optimized for speed and execution. ITSM platforms are optimized for compliance, auditing, risk mitigation, and operational control.
  2. “Configure Everything” Means “Mess Up Everything”: ServiceNow is essentially a relational database with a UI and workflow engine built on top of JavaScript. Because enterprises can customize every single rule and form field, internal IT admins frequently end up building over-engineered, slow processes.
  3. It Functions as Enterprise Middleware: Half of ServiceNow’s complexity isn’t the ticketing UI; it’s the background plumbing connecting legacy ERPs (SAP, Oracle), HR systems (Workday), cloud providers (AWS, Azure), and Active Directory.

What Makes ITSM Big Beyond Issue Tracking?

Calling ITSM an “issue tracker with hooks” is technically accurate at the micro level, but at enterprise scale (e.g., 20,000 to 100,000+ employees), three core architectural problems transform basic issue tracking into a multi-million-dollar necessity:

1. The Configuration Management Database (CMDB)

In a large organization, tracking what hardware, cloud infrastructure, and software dependencies exist across dozens of siloed departments is non-trivial. Without a CMDB, updating a single database schema might break an internal billing API and knock out a customer portal without anyone knowing who owns the broken component. A CMDB maps those relationships so root causes and blast radiuses can be analyzed immediately.

2. Change Management & Blast Radius Control

Engineers prefer shipping code straight to production, but enterprise executives and compliance auditors are terrified of outages and security breaches. ITSM enforces a formal Change Request process: Who is changing what? What is the rollback plan? What is the risk score? Has the Change Advisory Board (CAB) approved it? It converts human decisions into an auditable trail.

3. Cross-Departmental Workflows (Enterprise Service Management)

An issue tracker handles Task A assigned to Person B. ITSM orchestrates multi-departmental business processes. For example, onboarding a new employee requires HR to create a profile, IT to provision a laptop and grant VPN access, Facilities to issue a building badge, and Finance to set up payroll. An ITSM system orchestrates this single trigger across different software ecosystems without requiring endless email threads.

The Takeaway: Adding vs. Removing Complexity

+-----------------------------------------------------------------------+
|  PERSPECTIVE    |  WHAT THEY SEE IN SERVICENOW                        |
+-----------------+-----------------------------------------------------+
|  Developer / IC |  Added Complexity: Slow UI, bureaucratic gates,     |
|                 |  unnecessary form fields, friction to building.     |
+-----------------+-----------------------------------------------------+
|  CIO / Auditor  |  Reduced Complexity: One single pane of glass for   |
|                 |  security, compliance, costs, and assets.           |
+-----------------+-----------------------------------------------------+

ServiceNow does not reduce complexity for the individual worker—it increases operational friction. Instead, it absorbs and centralizes organizational complexity for leadership, trading developer ergonomics for enterprise control and auditability.


Q: What exactly is a CMDB? Is it a generic term or specific to ServiceNow?

CMDB stands for Configuration Management Database. It is a generic IT term and concept, not proprietary to ServiceNow.

Origin

The term comes from ITIL (Information Technology Infrastructure Library), a framework for managing IT services developed in the 1980s and 90s by the UK government. ITIL defined the CMDB as a central repository storing information about all components needed to deliver an IT service.

Every major enterprise ITSM platform implements its own CMDB:

  • ServiceNow: ServiceNow CMDB
  • BMC Helix: Atrium CMDB
  • Atlassian Jira: Asset & Configuration Management (formerly Insight)
  • Ivanti: Ivanti Neurons for Discovery / CMDB

Configuration Items (CIs) and Relationships

In a basic asset management spreadsheet, you record what you bought (e.g., “100 Dell Laptops”). A CMDB tracks Configuration Items (CIs) and, crucial for IT operations, the relationships and dependencies between them.

CIs include:

  • Hardware: Physical servers, routers, firewalls, endpoints.
  • Cloud Infrastructure: AWS S3 buckets, EC2 instances, Kubernetes clusters, VPCs.
  • Software & Applications: PostgreSQL instances, microservices, internal apps.
  • Business Services: “Payment Gateway,” “Payroll System,” or “East Coast Data Center.”

If an engineer files a Change Request to upgrade a database instance, the CMDB traces dependencies to warn: “Upgrading this database affects the Payment Gateway API, which impacts the Customer Checkout Service.”

Why Engineers Dislike Maintaining Them

In practice, CMDBs are notoriously difficult to keep accurate. If someone provisions cloud infrastructure via Terraform or manually without updating the record, the data goes stale. Modern platforms run automated discovery agents to continuously scan networks and cloud APIs, but keeping a CMDB clean remains a major operational challenge.


Q: Why do CIOs actually buy these platforms? Is it really about serving the business, or is it about corporate optics and risk reduction?

The preference for massive enterprise software suites often comes down to the incentives built into corporate structures—what many call the Enterprise IT Iron Triangle: offloading liability, protecting budgets, and building organizational scale.

1. Risk Offloading (“Nobody Ever Got Fired For Buying IBM”)

For an executive, selecting software is rarely driven by what makes individual engineers most productive. It is driven by risk mitigation.

If a CIO implements ServiceNow and a major outage or security audit occurs, they can demonstrate to the board and auditors that they are using an industry-standard, SOC2-compliant platform with established ITIL processes. If they instead build a lightweight internal developer portal using custom scripts, and a major audit failure occurs, the personal liability falls directly on leadership.

2. The Budget and Headcount Dynamic

In many enterprise organizations, executive compensation, title, and internal influence are linked to managed budget size and team headcount:

  • The Lightweight Route: Implementing lightweight developer tools might require a small team of platform engineers.
  • The Enterprise Suite Route: Implementing and maintaining a footprint like ServiceNow typically requires dedicated platform admins, specialized developers, business analysts, and multi-million-dollar implementation projects with external consulting firms.

Choosing the complex route inflates the department’s operational footprint, justifying larger annual budget allocations.

3. Compliance as an Audit Shield

In regulated industries (finance, healthcare, government), auditors demand complete traceability. They need proof that every code deployment or infrastructure change had an associated ticket, risk assessment, approval, and rollback strategy.

The operational friction that developers experience as “bloat” is, to a compliance officer, the exact evidence needed to demonstrate that control gates are functioning.


Conclusion

The tension around platforms like ServiceNow stems from a fundamental disconnect in goals:

  • Developers & Engineers value velocity, clean APIs, low friction, and minimal administrative overhead.
  • Enterprise Leadership & Auditors value risk mitigation, auditability, centralized governance, and predictable control.

ServiceNow did not become a $100B+ enterprise software company solely by solving technical problems—it grew by aligning directly with the political, regulatory, and risk-management incentives of enterprise executive teams.

Posted in Computers, Software | Leave a comment

Introducing Elections Manager: run your next election electronically, start to finish

Every board election, HOA vote, or association ballot tends to run into the same problems: mailed ballots that get lost or arrive late, volunteers spending a weekend hand-counting paper, and no real way to know who’s voted until it’s too late to remind them. We built Elections Manager to fix that.

What is it?

Elections Manager is a cloud-based SaaS for running electronic elections (or any poll for that matter) — no software to install, no committee IT project, and no monthly subscription. You pay only when you actually run an election.

How it works

Running an election takes seven steps, all inside the app:

  1. Create an election.
  2. Add ballot items — each one has a position (President, Board Member, a bylaws vote, whatever your organization needs) and the candidates or options running for it.
  3. Upload your voter list — just an email address plus first and last name for each voter.
  4. Set the date polling opens and the date it closes.
  5. Choose a scoring method for each item: plurality or ranked-choice.
  6. Voters get their ballot by email and vote electronically during the polling window.
  7. Compile and email results whenever you’re ready — one click.

Built for how real organizations vote

We support two scoring methods, so you can pick whichever fits the race being contested — no separate setup required:

  • Plurality — most votes wins.
  • Ranked-choice — voters rank candidates, and the lowest performer’s votes redistribute until someone has a majority.

And it’s built for the kinds of organizations that actually run these elections: HOAs and condominium associations, professional and trade associations, nonprofits and member organizations, clubs, cooperatives and alumni groups, private-company board and shareholder votes — and everyone else with a group that needs to vote on something.

Why go electronic

  • No mail-in ballots. No waiting on the mail, no printing or postage costs.
  • No ballots lost or discarded in transit.
  • Results in minutes, not a weekend of volunteers hand-counting paper.
  • Every vote is tied to a verified voter on your uploaded list — no duplicate ballots.
  • Turnout visible automatically — no hand-tallying who’s voted so far.

Pay only for what you use

There’s no monthly subscription — pricing is pay-as-you-go, per election:

Voters Price per election
Up to 25 $1
25+ $1–$3/voter depending on volume

You’ll always see the final price before checkout.

Get started

Create your organization, build your first ballot, and upload your voter list — you can be ready to open polling in minutes.

Get started →

Posted in Computers, programming, Software | Leave a comment

Demystifying tsconfig.json: Target vs. Module vs. ModuleResolution vs. esModuleInterop

If you’ve ever opened a tsconfig.json file, tweaked a setting, and prayed your build didn’t break, you’re not alone. TypeScript’s compiler settings can feel like a labyrinth of overlapping keywords. Four options in particular cause endless head-scratching:

  • target
  • module
  • moduleResolution
  • esModuleInterop

While they all sound like they’re doing the exact same thing (handling JavaScript modules), they actually control very distinct phases of compilation and module resolution.

Here is the ultimate mental model to untangle them once and for all.


The Quick Mental Model

To keep them straight, assign each setting a specific role in your build pipeline:

Setting What Question Does It Answer? Analogy
target How modern should the output JavaScript syntax be? Choosing the language dialect
module How should file imports and exports look in the output JS? Choosing the delivery container
moduleResolution How does TypeScript find the imported file on disk? The map/GPS algorithm
esModuleInterop How do we bridge the gap between ESM and legacy CommonJS modules? An adapter plug

1. target: Modern Syntax vs. Legacy Compatibility

target dictates the version of JavaScript syntax TypeScript outputs when it strips out your types. It controls syntax features like async/await, arrow functions, classes, and optional chaining.

Important: target does not change how your import and export statements are written—unless your module setting depends on it!

Example

Imagine you write this TypeScript code:

const greet = (name: string) => {
  console.log(`Hello, ${name}?.length`);
};

  • "target": "ES2020" → Outputs modern arrow functions and template literals as-is.
const greet = (name) => {
  console.log(`Hello, ${name}?.length`);
};

  • "target": "ES5" → Transpiles everything down to old ES5-compatible code using regular functions and string concatenation.
var greet = function (name) {
  console.log("Hello, " + (name === null || name === void 0 ? void 0 : name.length));
};


2. module: The Module Format of the Output

While target controls JS syntax, module controls how files import and export other files in the generated .js output.

Common JS module systems include:

  • CommonJS (CJS): Uses require() and module.exports (traditional Node.js).
  • ES Modules (ESM): Uses import and export (modern browser & Node.js standard).

Example

Suppose your TypeScript code imports a helper:

import { add } from './math';
export const result = add(1, 2);

  • "module": "CommonJS"
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const math_1 = require("./math");
exports.result = (0, math_1.add)(1, 2);

  • "module": "ESNext" (or ES2022)
import { add } from './math';
export const result = add(1, 2);


3. moduleResolution: How TS Finds Your Files

moduleResolution tells the TypeScript compiler the algorithm to use when searching for a module when you write import something from 'location'.

It doesn’t affect your compiled JS code at all. It strictly exists for compile-time type checking.

Common Values:

  1. node10 (formerly node): Mimics older Node.js require() lookup behavior. Looks in node_modules, searches for index.js or package.json “main” fields.
  2. node16 / nodenext: Built for modern Node.js which supports both ESM and CJS natively. It respects package.json "exports" fields and requires file extensions (like .js) in relative import paths!
  3. bundler: Designed for apps using modern bundlers (Vite, Webpack, esbuild). It respects "exports" like node16, but relaxes rules like requiring explicit .js extensions in imports.

Example

import { helper } from './utils';

With node16, TypeScript will yell at you:

Relative import paths must end with an extension. You’d have to write ./utils.js (even though the source file is ./utils.ts).

With bundler, TypeScript lets ./utils pass without complaint because your bundler handles extension resolving for you.


4. esModuleInterop: Smoothing Over CJS & ESM Friction

Historically, CommonJS modules exported a single default value like this:

// lodash in CommonJS
module.exports = function () { /* ... */ };

ES Modules require explicit default exports (export default ...). When ESM code tries to import a CJS package, syntax friction happens:

// Standard ES Module spec requires this:
import React from 'react'; 

// But without esModuleInterop, TS forced you to do this for CJS libs:
import * as React from 'react';

Setting "esModuleInterop": true tells TypeScript to emit tiny helper functions in the JavaScript output so you can use standard import React from 'react' syntax seamlessly, even when importing legacy CommonJS modules.

Example

import express from 'express';

  • "esModuleInterop": false → Fails compilation or runtime error because express exports via module.exports, not an ES default export.
  • "esModuleInterop": true → TS wraps the require('express') in an __importDefault helper under the hood so your clean ES import default syntax just works!

Summary Checklist

When configuring your next project, ask yourself:

  1. target: How old are the browsers or Node runtime I am deploying to? (e.g., ES2022)
  2. module: What module system does my target runtime execute? (e.g., NodeNext for modern Node, ESNext for Vite/Frontend)
  3. moduleResolution: Who is bundling or running my code? (e.g., bundler for Webpack/Vite, nodenext for pure Node)
  4. esModuleInterop: Am I importing CommonJS libraries into an ESM project? (Set to true 99% of the time!)
Posted in Computers, programming, Software | Leave a comment

Cold Email Outreach Tips

The short answer is yes—it matters immensely. In the world of email deliverability, the difference between sending 10 and 1,000 emails a day isn’t just a matter of volume; it’s the difference between being seen as a human and being flagged as a bot.

Here is why your daily volume acts as a massive signal to Email Service Providers (ESPs) like Google and Outlook.

1. The “Spam Trigger” Thresholds

ESPs look for patterns. A normal business user typically sends between 30 and 100 emails a day, many of which receive replies.

  • 10 emails/day: You are invisible to spam filters. Even if your copy is a bit “salesy,” you’re operating within the behavior of a standard human user.
  • 1,000 emails/day: This is a huge red flag for a single mailbox. Without a high-reputation “warmed-up” domain and specialized infrastructure, sending this many emails will likely land you in the “Promotions” tab or the “Spam” folder within hours.

2. The Infrastructure Gap

To send 1,000 emails a day safely, you can’t just hit “send” from a standard Gmail account. You need:

  • Domain Reputation: New domains have a “honeymoon” period where ESPs watch them closely. Blasting 1,000 emails from a new domain is the fastest way to get it blacklisted permanently.
  • SPF, DKIM, and DMARC: These are technical authentication records. If these aren’t set up perfectly, 1,000 emails will look like a spoofing attack.
  • Warm-up: You have to gradually increase volume over weeks (e.g., 20 today, 40 tomorrow) to prove you aren’t a spammer.

3. Engagement Ratios

Spam filters calculate a “reputation score” based on your Reply Rate.

  • If you send 10 emails and 0 people reply, your “bounce/ignore” rate is negligible.
  • If you send 1,000 emails and only 5 people reply, your engagement rate is $0.5\%$. Filters will assume your content is unwanted (UCE – Unsolicited Commercial Email) and start blocking you.

Comparison at a Glance

Feature10 Emails / Day1,000 Emails / Day
Risk LevelNegligibleVery High
PersonalizationHigh (Manual)Low (Automated templates)
Setup NeededBasic EmailMultiple domains + Warm-up tools
Likely OutcomeHigh Inbox PlacementHigh Spam Placement (if unprepared)

The Verdict

If you need to reach 1,000 people, the professional way to do it isn’t sending 1,000 from one account. It’s sending 30–50 emails/day across 20–30 different “throwaway” domains that redirect to your main site. This spreads the “risk” and keeps your main business email safe.

Posted in Computers | Leave a comment

The Case of the Mysterious Systemd Restart (It Wasn’t a Crash)

A few days ago I noticed something odd in my Spring Boot app’s logs: a fresh “Initializing Spring embedded WebApplicationContext” line, out of nowhere, in the middle of otherwise normal operation. No stack trace. No OutOfMemoryError. No exception at all — just the app quietly starting up again, as if nothing had happened.

That’s the annoying kind of bug. When the app itself logs an error, you at least know where to look. When it logs nothing, you’re debugging a crime scene with no witnesses.

Here’s how I tracked it down.

Step 1: Ask systemd what it saw

Since the app is run as a systemd service (my-app.service), the first move was to check systemd’s own record of what happened:

systemctl status my-app.service

This showed the service as healthy and running, with an uptime that lined up with the restart — not hugely informative on its own, but it confirmed the service was in fact bounced, not just logging something weird.

Step 2: Pull the journal around the exact restart time

Application logs only tell you what the app chose to log. Systemd’s journal tells you what actually happened to the process — stops, starts, exit codes, signals.

journalctl -u my-app.service --since "<restart-time-minus-2min>" --until "<restart-time-plus-2min>"

e.g.:

journalctl -u my-app.service --since "2026-08-11 15:27:13 UTC" --until "2026-08-11 15:30:13 UTC" -o cat | cat

This is where the trail actually started. The output showed:

systemd[1]: Stopping my-app.service...
systemd[1]: my-app.service: Deactivated successfully.
systemd[1]: Stopped my-app.service.
systemd[1]: my-app.service: Consumed 20min 58s CPU time, 1.8G memory peak, 0B memory swap peak.
systemd[1]: Started my-app.service.

Two things stood out immediately:

  1. “Deactivated successfully” — this was not a crash. A crash shows up as a nonzero exit code or a kill signal. This was a clean stop, meaning something intentionally told the service to stop.
  2. 1.8G memory peak — worth noting for later, since my JVM heap was capped at -Xmx768m. The total footprint (heap + metaspace + thread stacks + native buffers) had crept well past that. Not the cause of this particular incident, but a red flag worth fixing separately.

Step 3: Widen the net — what else happened at that exact second?

A clean stop means something else on the system triggered it. So instead of filtering to just my service, I pulled the entire system journal for that window:

journalctl --since "<restart-time-minus-2min>" --until "<restart-time-plus-2min>"

This was the payoff. The full log showed a very telling sequence, all within the same second:

systemd[1]: Starting apt-daily-upgrade.service...
apt.systemd.daily: [unattended-upgrade running]
systemd[1]: Stopping other-service-A.service...
systemd[1]: Stopping other-service-B.service...
systemd[1]: Stopping my-app.service...
systemd[1]: other-service-A.service: Deactivated successfully.
systemd[1]: Started other-service-A.service.
systemd[1]: other-service-B.service: Deactivated successfully.
systemd[1]: Started other-service-B.service.
systemd[1]: my-app.service: Deactivated successfully.
systemd[1]: Started my-app.service.

Three completely unrelated services — all running on the JVM, all otherwise unconnected to each other — restarted within the same second, immediately after an unattended package upgrade. That’s not a coincidence; that’s a pattern.

Step 4: Confirm the suspect — needrestart

That pattern is the signature of needrestart, a tool that ships with Ubuntu’s unattended-upgrades setup. Its job: after a security patch updates a shared library (things like libssl, libtinfo, libnghttp2), it scans running processes to see which ones are still linked against the old version of that library still sitting in memory, and restarts them so they pick up the patched version.

A quick check of the unattended-upgrades log confirmed the timing lined up exactly with a batch of library upgrades:

cat /var/log/unattended-upgrades/unattended-upgrades.log
INFO Packages that will be upgraded: libncurses6 libnghttp2-14 libtinfo6 ...
INFO All upgrades installed

And checking needrestart’s config confirmed it runs in fully automatic mode when triggered by apt:

cat /etc/needrestart/needrestart.conf | grep -i restart
# UBUNTU: the default restart mode when running as part of the APT hook is 'a',

'a' = automatic. No prompt, no notification, no log line in my app’s own output — it just quietly restarts anything it flags, which is exactly why the only trace in the application log was a fresh startup banner.

Mystery solved: my three JVM-based services all got swept up in a routine security patch restart, triggered by needrestart after unattended-upgrades patched some shared libraries.

Takeaways

  • A clean restart with no app-level error is a strong signal to look outside the application — systemd, the kernel, or another process is a more likely culprit than your own code.
  • journalctl -u <service> narrows the view; a plain journalctl (no unit filter) widens it. The second one was the key here — it revealed the surrounding system activity that the per-service view couldn’t show.
  • If multiple unrelated services restart at the exact same moment, look for a shared dependency, not a shared bug. In this case the shared thread was “runs on the JVM,” and the trigger was a routine library patch.
  • Automatic security patching is generally a good thing. Once I understood the cause, the fix wasn’t to disable it — it was simply to know that this kind of brief, clean restart is expected behavior, not an incident.

Debugging checklist for “my service restarted and there’s no error”

  1. systemctl status <service> — is it actually a clean stop or a crash/kill?
  2. journalctl -u <service> --since ... --until ... — narrow to the exact window
  3. journalctl --since ... --until ... (no unit filter) — widen to see the whole system at that moment
  4. dmesg -T | grep -i oom and journalctl -k | grep -i oom — rule out the OOM killer
  5. systemctl show <service> -p MemoryMax — rule out a cgroup memory limit
  6. Check /var/log/unattended-upgrades/ and needrestart config if the timing lines up with routine patching

Sometimes the most reassuring outcome of a debugging session isn’t finding a bug — it’s finding out there wasn’t one.

Posted in Computers, programming, Software | Leave a comment

certbot tips

Certbot

what is the difference between running certbot with –nginx vs –webroot?

https://grok.com/c/d50b69ef-d55c-4e15-b38e-53e3bf66edd1?rid=64adda4a-61ed-4173-8732-d40794c0d0ba

The main difference is:

  • --nginx = Authenticator + Installer (fully automatic for Nginx)
  • --webroot = Only Authenticator (manual configuration required for Nginx)

1. --nginx (Nginx plugin)

This is the easiest and most automated option when you’re using Nginx.

  • What it does:

    • Automatically modifies your Nginx config files to handle the ACME HTTP-01 challenge (serves the validation files).
    • Obtains the certificate.
    • Automatically installs the certificate (adds ssl_certificate, ssl_certificate_key, etc. to your server blocks).
    • Reloads Nginx.
  • Pros:

    • Very convenient — one command does everything.
    • Great for simple setups.
  • Cons:

    • It edits your Nginx configuration (makes backups, but still modifies files).
    • Less flexible if you have complex Nginx setups (reverse proxies, multiple includes, Docker, etc.).
    • Can sometimes produce less clean config than hand-written.

Typical command:

sudo certbot --nginx -d example.com -d www.example.com

2. --webroot (Webroot authenticator)

This is the most flexible method.

  • What it does:

    • Places a temporary validation file in a directory you specify (-w /path/to/webroot).
    • You must configure Nginx yourself to serve that directory at /.well-known/acme-challenge/.
    • Only gets the certificate (certonly mode by default) — does not install it into Nginx.
  • Pros:

    • Doesn’t touch your Nginx config.
    • Works great with complex setups, Docker, reverse proxies, CDNs, etc.
    • More predictable and controllable.
    • Preferred by many advanced users and in containers.
  • Cons:

    • You have to manually configure Nginx to serve the challenge files.
    • You have to manually install the certificate into your Nginx config.

Typical command:

sudo certbot certonly --webroot \
  -w /var/www/html \
  -d example.com \
  -d www.example.com

Comparison Table

Feature --nginx --webroot
Modifies Nginx config Yes (automatic) No
Needs manual Nginx setup No Yes (for .well-known/acme-challenge)
Certificate installation Automatic Manual
Best for Simple setups Complex, Docker, reverse proxies
Flexibility Lower Higher
Renewal Works automatically Works automatically

Recommendation

  • Use --nginx if your setup is straightforward and you want maximum convenience.
  • Use --webroot if you:
    • Have a complex Nginx config
    • Run Nginx in Docker
    • Want full control
    • Are behind a CDN/reverse proxy

Many experienced users prefer --webroot even on standard servers because it keeps Certbot from touching their carefully crafted Nginx configurations.


This is what happens exactly when you use --webroot.

Consider:

sudo certbot certonly --cert-name your-domain.com --webroot -w /some/path -d comma-separated-domains -v --dry-run
  • Step 1. For each domain, certbot client running on your computer is going to place a random file under /some/path.
  • Step 2. The certbot service will then attempt to read this file e.g.: https://your-domain/.well-known/acme-challenge/BG2yBJEAsS1FwvRFgSLp6zeH05G7bGShSpSkVX6gXj8
  • Step 3. If the service is able to read the file (200 OK), you are authenticated and the service will issue a certificate.

This means your NGINX must be configured as follows:

server {
    server_name your-domain;

    location /.well-known/acme-challenge/ {
        root /some/path;
        try_files $uri =404;
    }
}

If you do not explicitly specify the root e.g. our NGINX config uses:

# Allow standard well-known endpoints
location ^~ /.well-known/ { try_files $uri =404; }

NGINX will fallback to the root with which it was compiled. This can be found by running:

$ nginx -V 2>&1 | grep --color 'prefix='

Look for --prefix= — the default root is {prefix}/html. If you installed NGINX using apt-get then --prefix=/usr/share/nginx and so you should use -w /use/share/nginx/html in arguments to certbot.

The catch – if there is no server block for the domain, NGINX will fallback to the default server block and with the factory-installed config that comes with NGINX the root is set to /var/www/html so this is what happens when you run:

certbot -w /use/share/nginx/html ...

with a domain that is NOT explicitly covered by a server block:

  • Step 1. The client places the file under /use/share/nginx/html. This is the directory you gave to certbot under -w argument.
  • Step 2. The remote service tries to read the file https://your-domain/.well-known/acme-challenge
  • Step 3. There is no server block in your NGINX covering your-domain so NGINX falls back to the default server
  • Step 4. NGINX tries to serve the file from /var/www/html. This is the root in the default server block.
  • Step 5. The file is not found and NGINX returns 404
  • Step 6. The verification fails

Migrating sites from one server to another

The problem: How to migrate a site from one server to another and install TLS certificate on new server before switching the DNS

Solution: Proxy request from old server to new one so certbot verification can complete. Insert this block on the old server:

    # 1. Catch the Let's Encrypt challenge and proxy it to the new server
    location /.well-known/acme-challenge/ {
        proxy_pass http://IP-ADDRESS-OF-NEW-SERVER;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
Posted in Computers, programming | Leave a comment