Essofore Semantic Search — Self-Hosted RAG Infrastructure That Keeps Your Data in Your VPC

Upload documents. Search in plain English. Your data never leaves your AWS account.

Most vector search infrastructure has a hidden problem: your data lives somewhere else. Whether it’s Pinecone’s servers or a managed Elasticsearch cluster, your proprietary documents are outside your control.

Essofore is different. It deploys as an AMI directly into your AWS VPC. Your documents never touch our servers or anyone else’s. And unlike Elasticsearch — which charges more as your data grows because vectors must be loaded into RAM — Essofore’s cost stays flat no matter how much you index for a given EC2 instance.

Who this is for:

  • Developers at healthcare, fintech, or legaltech companies who can’t send proprietary documents to a SaaS vendor
  • Teams getting unexpected bills from Elasticsearch or managed vector DBs as their corpus grows
  • Developers who want RAG search without becoming an ML expert — no embedding knowledge required

What makes it different:

  • Data sovereignty: 100% self-hosted in your AWS account. No third party ever sees your data.
  • Transparent pricing: $0.55/hour + Cost of EC2 independent of the data volume
  • Zero ML expertise required: Upload PDFs, Word docs, HTML. Essofore handles chunking, embeddings, vector storage, and search automatically.
  • Enterprise-grade simplicity: One AMI. One systemctl command. Ready to serve queries.

Typical use cases:

  • Internal enterprise search over company knowledge bases
  • RAG retrieval layer for LLM-powered applications
  • HIPAA/GDPR-compliant AI search where data residency matters

Get Started on AWS.

Posted in Computers, programming, Software | Leave a comment

Note to self: For God’s sake stop renaming things

There are only two hard things in Computer Science: cache invalidation and naming things. — Phil Karlton

I don’t think there is anyone on the planet who renames things as much as I do. Stop it. Seriously. Its not worth it.

It looks innocuous. But it isn’t.

I had a project that was working just fine but being me I renamed the directory and all of a sudden everything stopped working. Turns out it was a Python project using uv (you run into same problem if you use poetry). uv hardcodes the path in .venv/bin/activate. Look for this line:

VIRTUAL_ENV='/path/to/.venv'

So if you rename a directory and then run source .venv/bin/activate or any other uv command, it will stop working!

It doesn’t end there. I was using uvicorn and the path is hardcoded in that too. What makes it hard to debug is that the errors you get are extremely cryptic like File not found when you can see it right in front of your eyes.

Posted in Computers, programming, Software | Leave a comment

ETFs vs. Mutual Funds

This post is AI generated

Are ETFs more tax-efficient than their equivalent mutual funds?

Yes, ETFs are generally more tax-efficient than their equivalent mutual funds, especially in taxable brokerage accounts. This advantage holds for both passive (index-tracking) and many active strategies, though the gap is most pronounced in equity funds.

Why ETFs Tend to Be More Tax-Efficient

The key difference comes down to structure and how shares are created/redeemed, not the underlying investments or tax rules themselves (both are taxed the same way on dividends and realized capital gains when you sell).

  • Mutual funds: When investors redeem shares, the fund often sells underlying securities for cash to pay them out. If those sales generate gains (common in rising markets or with high turnover), the fund must distribute those capital gains to all shareholders at year-end—even if you didn’t sell anything. You owe taxes on those distributions (usually long-term capital gains rates).
  • ETFs: Most use an in-kind creation/redemption process. Large institutional investors (authorized participants) exchange baskets of securities for ETF shares (creation) or vice versa (redemption). No cash changes hands at the fund level in these large blocks, so the ETF avoids selling securities and realizing gains that would need to be distributed. Investor trading on the exchange also doesn’t force the fund to sell holdings.

This results in far fewer (or zero) capital gains distributions from ETFs.

Real-World Data on Tax Efficiency

Recent figures confirm the structural edge:

  • In 2025, only 7% of ETFs distributed capital gains, compared to 52% of mutual funds. Long-term averages (since 2016) are about 9% for ETFs vs. 53% for mutual funds.
  • Among passive funds: Just 4% of passive ETFs distributed gains vs. 41% of passive mutual funds.
  • Active ETFs also outperform active mutual funds on this metric (9% vs. 53% in 2025).

Even when ETFs do distribute gains, the amounts are typically much smaller. Over multi-year periods, this can mean meaningful after-tax return differences for buy-and-hold investors in taxable accounts.

Other Tax Considerations

  • Dividends and interest: Both ETFs and mutual funds distribute these, and they’re taxed similarly (qualified dividends at favorable rates for many investors).
  • Your own sales: You pay capital gains tax only when you sell shares in either vehicle. ETFs trade like stocks (intraday), giving you more control over timing, but this doesn’t create fund-level distributions.
  • Exceptions and caveats:
    • Vanguard’s structure: Many Vanguard mutual funds have an ETF share class that’s essentially the same fund; tax efficiency benefits can flow to both.
    • Bond/fixed-income funds: The advantage is smaller because income (interest) is the main taxable event anyway, and turnover is often lower.
    • Highly active or niche strategies: Some mutual funds use tax-management techniques (loss harvesting, etc.) that can narrow the gap, but ETFs still usually win on average.
    • Tax-advantaged accounts (IRAs, 401(k)s, Roths): Tax efficiency doesn’t matter here—gains and distributions aren’t taxed until withdrawal (or never, in Roths). Choose based on fees, liquidity, or other features.
    • ETFs aren’t immune: They can still have capital gains in rare cases (e.g., major index changes, mergers, or certain commodity/actively managed funds).

Bottom Line

For taxable accounts, equivalent ETFs (same index or strategy) are typically the more tax-efficient choice due to minimized unwanted capital gains distributions. This can compound into higher after-tax returns over time without any extra effort on your part. In tax-sheltered accounts, the difference is irrelevant.

Always check a specific fund’s history of capital gains distributions (available on fund fact sheets or tax guides) and consult a tax advisor for your situation, as individual results depend on your tax bracket, holding period, and market conditions.

Posted in Money | Leave a comment

Site Refresh

Before and After:

PageSpeed Insights (New):

Please help me evaluate the old vs. new site by giving your feedback below:

Old: https://staging.nuvoice.ai/

New: https://www.nuvoice.ai/

On a scale of 1-10 where:

  • 1–3 = Barely functional / confusing / unprofessional
  • 4–6 = Basic but has clear issues (common for early drafts)
  • 7–8 = Solid and competitive (ready for public launch with minor tweaks)
  • 9–10 = Excellent, polished, high-converting landing page

How would you rate each of them?

Posted in Computers, programming, Software | Leave a comment

Building the AWS Marketplace SaaS Fulfillment Flow (ResolveCustomer and Beyond)

When a customer clicks “Subscribe” on your AWS Marketplace listing, AWS redirects them to your application with a token. Your job is to validate that token, create a tenant, and get the customer into your product. It sounds like a simple redirect. It isn’t.

The fulfillment flow touches CORS, cross-site cookies, JWT session management, race conditions with SQS, and a token validation API that only works in one region. I’ve built this flow for 4 AWS Marketplace SaaS products. Here’s everything I wish someone had told me upfront.

How the Flow Works

The end-to-end sequence looks like this:

1. Customer clicks Subscribe on AWS Marketplace
2. AWS shows a confirmation page, customer clicks "Set Up Your Account"
3. AWS POSTs an HTML form to your fulfillment URL with a registration token
4. Your server calls ResolveCustomer to validate the token
5. You create a tenant record in your database
6. You redirect the customer to your signup page
7. Customer creates their admin account (email + password)
8. Customer is now in your product

Steps 3 through 7 are where all the complexity lives.

Step 1: Receiving the POST from AWS

When the customer clicks “Set Up Your Account,” AWS submits an HTML form POST from aws.amazon.com to your fulfillment URL. The body is URL-encoded with two fields:

Field Description
x-amzn-marketplace-token Opaque registration token. You’ll pass this to ResolveCustomer.
x-amzn-marketplace-offer-type "free-trial" for trial offers. Absent for paid subscriptions.

This is a cross-origin POST from Amazon’s domain to yours. That means you need CORS headers:

const cors = require('cors');

router.post('/', cors({
    origin: process.env.NODE_ENV === 'production'
        ? 'https://aws.amazon.com'
        : '*'
}), async (req, res) => {
    const regToken = req.body['x-amzn-marketplace-token'];
    const offerType = req.body['x-amzn-marketplace-offer-type'] || 'paid';
    // ...
});

Lock the CORS origin to https://aws.amazon.com in production. In development, allow anything so you can test with a mock.

One nuance: the CORS check happens in the browser, not on your server. Your server has no way to enforce CORS – it just sets the Access-Control-Allow-Origin header and the browser decides whether to allow the response. In practice, since your response is a 303 redirect, browsers follow it regardless of CORS. But setting the header is still good practice.

Step 2: Calling ResolveCustomer

The token from AWS is opaque. You can’t decode it yourself. You must call ResolveCustomer to exchange it for the customer’s identity:

const { MarketplaceMeteringClient, ResolveCustomerCommand } = require('@aws-sdk/client-marketplace-metering');

const client = new MarketplaceMeteringClient({ region: 'us-east-1' });

const response = await client.send(
    new ResolveCustomerCommand({ RegistrationToken: regToken })
);

const acctId = response.CustomerAWSAccountId;   
const custId = response.CustomerIdentifier;     
const productCode = response.ProductCode;       

Three things come back:

  • CustomerAWSAccountId – The customer’s 12-digit AWS account ID. This is your primary tenant identifier.
  • CustomerIdentifier – A shorter identifier used by the Metering and Entitlement APIs. You need both.
  • ProductCode – Your product’s code. Validate this matches your configured product code to prevent cross-product token replay.

Validate all three fields are present, and verify the product code matches yours:

if (!acctId || !custId || !productCode) {
    return renderErrorPage(res, 'Registration failed. Please try again from AWS Marketplace.');
}
if (productCode !== PRODUCT_CODE) {
    return renderErrorPage(res, 'Invalid product code.');
}

Return HTML error pages here, not JSON. The response goes to a browser that was just redirected from Amazon.

Step 3: Handling Returning Customers

Not every POST to your fulfillment URL is a new customer. A customer might click through from AWS Marketplace again after they’ve already registered. You need to handle three cases:

const existingTenant = db.customers.getByAwsAcctId(acctId);

if (existingTenant && existingTenant.email) {
    // Fully registered. Redirect to the app.
    return res.redirect(303, '/app');
}

if (existingTenant && !existingTenant.email) {
    // Tenant exists but admin never completed signup.
    // Give them a fresh registration session.
    return createRegistrationSession(custId, acctId, productCode, res);
}

// Brand new customer. Create tenant and start registration.
const subscriptionStatus = deriveSubscriptionStatus(custId);
db.customers.add(acctId, custId, offerType, subscriptionStatus);
createRegistrationSession(custId, acctId, productCode, res);

The middle case – tenant exists but no email – happens when a customer clicked through from AWS, you created the tenant row, but they closed their browser before completing the signup form. Give them a fresh session and let them try again.

Step 4: The Registration Session

After creating the tenant, you need to get the customer to a signup page where they create their admin account. But you need to carry the registration context (who this AWS customer is) from the POST handler to the signup page without exposing it in a query string.

The solution: a short-lived JWT stored in an httpOnly cookie.

const crypto = require('node:crypto');
const jwt = require('jsonwebtoken');

function createRegistrationSession(custId, acctId, productCode, res) {
    const jti = crypto.randomUUID();

    // Store in the database for one-time-use enforcement
    db.registrationSessions.store(jti, custId, acctId);

    // Sign a short-lived JWT
    const regJwt = jwt.sign(
        { jti, acctId, pc: productCode },
        JWT_SECRET,
        {
            algorithm: 'HS256',
            audience: 'awsmp-register',
            issuer: APP_NAME,
            expiresIn: REG_SESSION_TTL_SEC
        }
    );

    // Set it as an httpOnly cookie
    res.cookie('reg', regJwt, {
        httpOnly: true,
        secure: true,
        sameSite: 'lax',
        path: '/',
        maxAge: REG_SESSION_TTL_SEC * 1000,
    });

    return res.redirect(303, '/admin/signup');
}

The database table backing this:

CREATE TABLE registration_sessions (
    jti TEXT PRIMARY KEY,
    cust_id TEXT NOT NULL,
    tenant_id TEXT NOT NULL,
    used INTEGER NOT NULL DEFAULT 0,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

Three things to notice:

1. sameSite: 'lax', not 'strict'. This is the gotcha that will cost you hours of debugging. The customer is being redirected from aws.amazon.com to your domain. With sameSite: 'strict', the browser won’t send the cookie on a cross-site redirect. lax allows it on top-level navigations (which a 303 redirect is). Your main auth cookie should still use strict – only the registration cookie needs lax.

2. The session is one-time-use. The used flag prevents the same registration session from being consumed twice. After the customer completes signup, mark it used. Without this, someone could replay the reg cookie to create additional admin accounts.

3. Dual expiry. The JWT has its own expiry (expiresIn), and the middleware also checks the database created_at against the TTL. Belt and suspenders. If the JWT secret is compromised, the database check still protects you.

Step 5: The Admin Signup

When the customer lands on the signup page, the reg cookie is present. Your signup endpoint validates it:

function requireRegistrationSession(req, res, next) {
    const cookie = req.cookies?.reg;
    if (!cookie) {
        return res.status(403).send('Start from AWS Marketplace to register.');
    }

    try {
        const payload = jwt.verify(cookie, JWT_SECRET, {
            audience: 'awsmp-register',
            issuer: APP_NAME
        });

        const row = db.registrationSessions.get(payload.jti);

        if (!row || row.used) {
            return res.status(403).send('Registration link expired.');
        }

        // Check TTL against database timestamp (belt + suspenders)
        const sessionAge = Date.now() - new Date(row.created_at).getTime();
        if (sessionAge > REG_SESSION_TTL_SEC * 1000) {
            return res.status(403).send('Registration link expired.');
        }

        req.registration = { jti: payload.jti, awsAccountId: row.tenant_id };
        next();
    } catch (e) {
        return res.status(403).send('Invalid registration session.');
    }
}

Then the admin signup handler creates the user account, sets the auth cookie, and clears the registration cookie – all in a single database transaction:

router.post('/admin-signup', requireRegistrationSession, async (req, res) => {
    const { email, password } = req.body;
    const acctId = req.registration.awsAccountId;

    db.transaction(() => {
        db.users.create({
            tenantId: acctId,
            email,
            hash: bcrypt.hashSync(password, SALT_ROUNDS),
            role: 'admin'
        });
        db.customers.updateEmail(acctId, email);
        db.registrationSessions.markUsed(req.registration.jti);
    });

    // Issue the real auth cookie (sameSite: strict this time)
    const token = jwt.sign({ acctId, email, role: 'admin' }, JWT_SECRET, {
        expiresIn: JWT_EXPIRATION_HOURS + 'h'
    });
    res.cookie('token', token, {
        httpOnly: true,
        secure: true,
        sameSite: 'strict',
    });

    // Clear the registration cookie
    res.clearCookie('reg', { path: '/', sameSite: 'lax', secure: true });

    res.status(201).json({});
});

Notice the cookie upgrade: the reg cookie was sameSite: 'lax' (needed for the cross-site redirect). The token auth cookie is sameSite: 'strict' (it’s only ever used same-site from now on). The customer transitions from a loose-security registration context to a tight-security authenticated context.

Step 6: The SQS Race Condition

There’s one more thing. When a customer subscribes, AWS also sends a subscribe-success message to your SQS queue. These two events – the browser redirect and the SQS message – are completely independent. The SQS message can arrive before the customer visits your registration URL.

If your SQS handler tries to UPDATE the tenant’s subscription status but the tenant row doesn’t exist yet, the UPDATE silently affects 0 rows. The SQS message is deleted from the queue. And now you’ve lost the event.

The fix: always save SQS events to an audit table first, then attempt the UPDATE. At registration time, derive the initial subscription status from the event history:

// At registration time (new customer)
const latestEvent = db.subscriptionEvents.getLatestByCustomer(custId);
const subscriptionStatus = latestEvent?.action === 'unsubscribe-success'
    ? 'unsubscribed'
    : 'subscribed';

db.customers.add(acctId, custId, offerType, subscriptionStatus);

I wrote a full blog post about this race condition if you want the details.

The Full Picture

Here’s the complete flow with every security boundary:

AWS Marketplace (aws.amazon.com)
  |
  | HTML form POST (x-amzn-marketplace-token)
  | CORS: Access-Control-Allow-Origin: https://aws.amazon.com
  v
POST /register
  |-- ResolveCustomer (us-east-1) --> acctId, custId, productCode
  |-- Validate productCode matches
  |-- Check: returning customer?
  |     yes + has email  --> 303 redirect to /app
  |     yes + no email   --> fresh registration session
  |     no               --> create tenant, derive subscription status from SQS events
  |-- Sign JWT (audience: awsmp-register, TTL: short)
  |-- Store session in DB (jti, cust_id, tenant_id, used=0)
  |-- Set 'reg' cookie (httpOnly, secure, sameSite: lax)
  |-- 303 redirect to /admin/signup
  v
GET /admin/signup (frontend signup form)
  |
  | User enters email + password
  v
POST /users/admin-signup
  |-- requireRegistrationSession middleware
  |     verify JWT, check DB row exists + not used + not expired
  |-- Transaction:
  |     INSERT user (email, password_hash, tenant_id, role=admin)
  |     UPDATE tenant email
  |     UPDATE registration_session used=1
  |-- Set 'token' cookie (httpOnly, secure, sameSite: strict)
  |-- Clear 'reg' cookie
  |-- 201 Created
  v
Customer is authenticated and in the product

Gotchas Summary

  1. sameSite: 'lax' on the registration cookie. strict blocks the cross-site redirect from Amazon. This is the #1 thing that trips people up.
  2. Validate the ProductCode. Prevents cross-product token replay.
  3. Handle returning customers. They’ll click through from AWS Marketplace again. Don’t error – redirect them.
  4. One-time-use registration sessions. The used flag prevents replay. The database check catches JWTs that are still valid by expiry.
  5. SQS race condition. Save events to an audit table first, reconcile at registration time. Don’t rely on the tenant row existing when SQS events arrive.
  6. Return HTML error pages, not JSON. The customer is in a browser redirected from Amazon. A JSON error response is useless to them.
  7. Free-trial offer type. Capture x-amzn-marketplace-offer-type at registration and store it. You’ll need it later for entitlement gating.

Takeaway

The ResolveCustomer fulfillment flow is the front door to your AWS Marketplace SaaS product. Every customer passes through it exactly once. It touches cross-origin security, cookie semantics, distributed event ordering, and session management – all in a single POST handler.

Get it right once, and you never think about it again. Get it wrong, and your customers can’t use your product.


I’ve packaged a production-tested implementation of this entire flow – plus auth, entitlements, metering, and a built-in admin panel – into a self-hosted Node.js gateway kit. If you’re listing a SaaS product on AWS Marketplace, check it out here.

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

How to protect Cloudfront hosted website from malicious traffic?

Problem

I was hosting a static homepage on Cloudfront and was surprised to see tens of thousands of requests to it per day:

These requests are coming from malicious bots. How do we confirm?

Investigation

The best way to be able to debug issues like this is to enable Cloudfront Standard logs and then analyze in Amazon Athena:

1. Enable CloudFront Standard Logs

Enable CloudFront Standard Logsnot available in free-plan associated with flat-rate pricing. So choose PAYG pay-as-you-go plan. This will unlock many other features and for low traffic websites is better in terms of cost than flat-pricing. However, note that once you are on the Free-Plan you cannot change to PAYG! You have to cancel the plan first [1]

  • Go to the CloudFront Console > Distributions > [Your ID].
  • Under General tab, click Edit.
  • Find Standard logging, turn it on, and select an S3 bucket to store them.
  • Wait: It takes about an hour for logs to start appearing.

2. Analyze the Logs with Amazon Athena

Once you have logs, don’t try to read them manually—they are messy. Use Amazon Athena to run a SQL query against the log files in S3.

Run this query to find the top “offenders”:

SELECT client_ip, count(*) as request_count, uri_path, user_agent
FROM cloudfront_logs
GROUP BY client_ip, uri_path, user_agent
ORDER BY request_count DESC
LIMIT 20;
  • If you see one IP with 10k requests: It’s a bot. You can block it via AWS WAF.
  • If you see requests for .php or /admin: It’s a vulnerability scanner.
  • If you see a specific image file being requested: Someone has likely embedded your image on their high-traffic site.

Analyzing 4xx traffic

It would be good to know what paths are causing 4xx. These are the paths malicious bots are trying to hit and we can create a rule (Solution 2) that blocks traffic based on what path is being requested. If we don’t have a /wp-admin on our site, anyone trying to access it must be a bot. Unfortunately, you can’t see a list of 4xx URLs/paths on the free-plan. What you can do as a best-effort is go to Reports and Analytics -> Popular Objects tab and sort it by 4xx descending:

Solution 1: Rate-limit

Goto your Cloudfront distribution in AWS console. Then from the Security tab select Manage Rules

By default you will see:

Click on Add Rule. Choose Rate-based rule and click Next

Configure as follows. Change rate-limit as you like. Click on Add Rule

Solution 2: BETTER: Block traffic trying to access bad paths (not available on free-plan)

Many bots try to access paths like /.env, /wp-admin etc. If you are hosting a static website you know these paths do not exist and anyone trying to access them is a malicious bot. So instead of rate-limiting, a better option is to just block anyone who tries to hit these paths that otherwise cause 4xx. Tip: Look for paths that cause 4xx errors and add them to the list. Goto Manage Rules and select Custom Rule

After that you would select URI path in below:

and block malicious bots hitting your website.

How much do Cloudfront logs cost?

Analyzing these logs is very affordable for a site with low traffic volume. CloudFront doesn’t charge you to generate the logs, but you pay for storing them in S3 and querying them with Athena.

Based on your current traffic (~30k requests/day), here is the cost breakdown:

1. S3 Storage (Negligible)

CloudFront logs are small. Even with 30,000 requests per day, you’re likely generating less than 100MB of logs per month.

  • Cost: Approximately $0.01 per month.
  • Math: S3 Standard is about $0.023 per GB. You aren’t even hitting 1 GB.

2. Athena Queries (Cheap)

Athena charges based on the amount of data scanned.

  • Cost: $0.01 per query (minimum charge).
  • Math: Athena charges $5.00 per TB scanned. Since your total logs for the month are likely under 1 GB, every query you run will cost the minimum scan fee (10MB), which is effectively a fraction of a penny.

3. S3 Requests (The “Hidden” Cost)

Every time CloudFront “puts” a log file into your S3 bucket, it’s an S3 PUT request.

  • Cost: Approximately $0.05 – $0.10 per month.
  • Math: S3 charges $0.005 per 1,000 PUT requests. CloudFront delivers logs in batches every few minutes.

Total Estimated Cost

For a site seeing ~1 million requests a month (your current trajectory), the total cost to log and debug this will be less than $0.50 USD per month.

Pro Tip: If you want to keep it free, set an S3 Lifecycle Policy on your log bucket to automatically delete files older than 7 or 14 days. This prevents “log bloat” from costing you money a year from now.

Flat-Rate Pricing Plans

Below are details of flat-rate pricing plans. Tip: Choose Pay-As-You-Go for low-traffic websites like homepages. It won’t be completely free but you get more features and worth the cost.


This post has been written with help from Gemini

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

Cannot connect to localhost from Chrome

Ran into this issue where all of a sudden I could not connect to localhost from Chrome. It was working and then all of a sudden it stopped working. It (Chrome) just hangs waiting for response from server. I was able to make curl requests successfully. What gives? Turned out to be a WSL issue (bug) in my case. Sometimes something inside WSL crashes and it breaks the networking. The fix is to shutdown WSL and start it again:

wsl --shutdown

The curl request was working because I was running it inside WSL. If I ran curl from Windows it gave same error.

Lately I have been seeing this happen more often so making a note to self.

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

What I am investing in right now

Based on this YouTube video. WDYT?

Using PEGY ratio ( PEGY < 1, P/E < 40, D/E < 1, ROE >= 20)

Top REITs (PEGY < 1, D/E < 1)

What is the problem investing in an ETF like SCHH? If you look at the holdings, the top holding is WellTower which is probably the worst holding in the whole portfolio:

Posted in Money | Leave a comment

Connecting external domain to WordPress

Why do we do this? so we can host the blog at blog.siddjain.com instead of sidshome.wordpress.com

The setting to do this is in Upgrades -> Domains

Tip: do NOT use https://wordpress.com/domains/manage. You won’t be able to connect a domain on that page.

Click on advanced setup

Click on Start setup

Click on I found the domain’s settings page

There are 2 A records and 1 CNAME record WordPress will ask you to add to your DNS Settings. Do that.

Wait 5 minutes for the records to propagate then click on Verify

We are not done yet. If you leave things as-is at this point, then your blog will be mirrored on two URLs – yourname.wordpress.com and other.domain.com. You don’t want this as this is really bad for SEO. You want the domain you connected to become the primary and yourname.wordpress.com should redirect to that.

When you set a domain as “Primary,” WordPress automatically adds a rel="canonical" tag to your site’s code and performs a 301 redirect from the .wordpress.com address to your custom URL.

  1. In your WordPress.com dashboard, go to UpgradesDomains (or HostingDomains).
  2. You will see a list of all addresses associated with your site.
  3. Look for blog.siddjain.com.
  4. Click the three dots (⋯) next to it or click the domain name directly.
  5. Select Make Primary.

What Happens Next?

  • Automatic Redirects: If someone types siddjain.wordpress.com/my-post, they will be instantly redirected to blog.siddjain.com/my-post.
  • Search Engine Instruction: Google will see the 301 redirect and the canonical tag, signaling that blog.siddjain.com is the “official” version to index.
  • SSL Certificate: WordPress.com will automatically generate an SSL certificate for your subdomain (this can take a few minutes to an hour after setting it as primary).

One Quick Tip for Google Search Console

Even with the redirect in place, it is a “best practice” to go to your Google Search Console account and add blog.siddjain.com as a new property. This allows you to monitor exactly how Google is indexing the new URL.

Luckily WordPress did the above switch to primary for me without me having to do anything. The final setup is like this:

And here are curl requests showing what is happening behind the scenes and verifying the site is not duplicated across two URLs – Google does not like that:

$ curl -I sidshome.wordpress.com
HTTP/1.1 301 Moved Permanently
Server: nginx
Date: Fri, 20 Feb 2026 00:46:47 GMT
Content-Type: text/html
Content-Length: 162
Connection: keep-alive
Location: https://sidshome.wordpress.com/
Alt-Svc: h3=":443"; ma=86400
Server-Timing: a8c-cdn, dc;desc=sea, cache;desc=BYPASS;dur=0.0
$ curl -I https://sidshome.wordpress.com
HTTP/2 301
server: nginx
date: Fri, 20 Feb 2026 00:46:55 GMT
content-type: text/html; charset=utf-8
location: https://blog.siddjain.com/
vary: Cookie
x-ac: 3.sea _bur MISS
alt-svc: h3=":443"; ma=86400
strict-transport-security: max-age=31536000
server-timing: a8c-cdn, dc;desc=sea, cache;desc=MISS;dur=35.0
$ curl -I http://blog.siddjain.com
HTTP/1.1 301 Moved Permanently
Server: nginx
Date: Fri, 20 Feb 2026 00:47:14 GMT
Content-Type: text/html
Content-Length: 162
Connection: keep-alive
Location: https://blog.siddjain.com/
Alt-Svc: h3=":443"; ma=86400
Server-Timing: a8c-cdn, dc;desc=sea, cache;desc=BYPASS;dur=0.0

Bonus: You can change the favicon (known as Site Icon) in Settings -> General

Further Reading

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

Which is your favorite Chatbot?

Posted in Computers | Leave a comment