# Server-side tagging migration: how to move without breaking your data

**Author:** Piotr Litwa - GTM & Analytics Specialist
**Published:** 2026-06-12
**URL:** https://piotrlitwa.com/articles/en/server-side-tagging-migration.html
**Language:** en
**Keywords:** ["server-side gtm migration", "migrate to server side tracking", "server side tagging setup", "gtm server side parallel run"]

---

A server-side tagging migration isn't a switch you flip. It's a **parallel run**: you stand up the server, send events to both the old and the new setup at the same time, reconcile the numbers until they agree, and only then turn the old one off.

Do it any other way and you will find out what broke by looking at a revenue chart, three weeks late.

The single most common failure I see is a big-bang cutover on a Friday. Everything looks fine in GTM Preview, tags fire, the server responds 200, and the team goes home. On Monday, purchases in GA4 are down 40% and nobody can say whether sales fell or tracking did. Both are plausible. That ambiguity is the expensive part, not the bug itself.

This article is the migration playbook: what to fix before you start, how to run both systems side by side, the reconciliation checklist, the five things that break, and how to roll back.

> **Key Takeaways**
> - Migrate with a parallel run, never a cutover. Both systems send data until the numbers reconcile.
> - Fix your client-side tracking first. Server-side doesn't repair broken tracking, it relocates it.
> - The number one silent killer is Meta Conversions API deduplication. Without a matching `event_id` on both sides, every purchase counts twice.
> - Consent state must be forwarded to the server explicitly. It doesn't travel by itself, and a server that never receives a denial will happily process it.
> - Expect a 2-8% gap between client-side and server-side event counts. That gap is ad blockers, and it's the point.

## Before you start: fix what is already broken

Server-side tagging relocates your tracking. It doesn't fix it.

If your `purchase` event fires twice today, it will fire twice on the server. If your consent implementation is wrong, moving it server-side makes it wrong in a place where nobody can see it in the browser console. You will have spent €2,000 and three weeks to reproduce your existing bugs in a more expensive architecture.

Run the checks first:

1. **Is anything double-tagged?** View source, search for `gtag(`. If it's there and GTM also has a GA4 configuration tag, fix that before you touch anything else.
2. **Does GA4 reconcile with your backend?** Take yesterday's orders from your database. Compare to GA4 purchases. If they are more than 5% apart, find out why now, not during a migration when every discrepancy gets blamed on the server.
3. **Does consent actually work?** Deny consent, reload, confirm nothing fires. If it fires anyway, you have a compliance problem that server-side will hide rather than solve.

This step is boring and everybody skips it. It's also the difference between a migration that takes three weeks and one that takes three months.

**Not sure what state your tracking is in?** The [Free GTM Audit](https://piotrlitwa.com/checkGTM/) runs these checks automatically. Enter a URL, get a report in about 10 minutes. No signup.

## Phase 1: Stand up the server, move nothing

Deploy the tagging server, point a subdomain at it, and stop.

You need `analytics.yoursite.com` (or similar) resolving to your server container, on your own domain, with a valid certificate. Not a Google-provided domain: if the cookie isn't set on your domain, you lose the entire duration benefit that justified the project.

At this point, zero production traffic uses it. That's correct. Resist the urge to move a tag "just to test."

**Cost check**: for most mid-size sites this lands between €50 and €200/month. Two things blow that number up: not enabling autoscaling minimums correctly, and logging every request to Cloud Logging by default. Turn request logging off unless you are actively debugging, or your logging bill will exceed your compute bill.

## Phase 2: The parallel run

This is the core of the migration and the part people skip.

**Create a second GA4 property.** Not a second data stream, a whole second property. Point your server-side GA4 tag at it. Your production property keeps receiving client-side data exactly as before.

Now both run. Client-side to Property A (production, untouched). Server-side to Property B (shadow). Same users, same events, two pipelines.

Let it run for at least **two weeks**. You need enough volume to compare, and you need at least one full weekly cycle, because weekday and weekend traffic behave differently and a three-day sample will lie to you.

Nothing in production changes. If the server catches fire, your data doesn't care.

### What to compare

Reconcile these, in order:

| Metric | Expected gap | If it's wildly off |
|---|---|---|
| **Page views** | Server-side 2-8% *higher* | Server-side higher is correct: ad blockers no longer block it |
| **Purchases** | Within 1-2% | A big gap means the ecommerce payload isn't surviving the trip |
| **Purchase revenue** | Within 1-2% | Check currency and value formatting, not event counts |
| **Users** | Server-side lower | Expected. Server-side cookies last longer, so the same person is counted as one user instead of three |
| **Consent-denied sessions** | Should behave identically | If the server records events for denied users, stop immediately |

That first row is the one that confuses people. **Server-side showing more traffic isn't a bug.** It's the ad blocker recovery you paid for. A 2-8% lift is normal. A 40% lift means something is firing twice.

## Phase 3: The five things that break

In order of how often I find them.

### 1. Meta Conversions API double-counting

The big one. If you send purchases from both the browser pixel and the server, Meta needs to know they are the same event. It matches them on **`event_id`** (browser) and **`event_id`** (server), plus `event_name` and timestamp.

Get this wrong and every purchase counts twice. Your ROAS doubles overnight, everyone celebrates, and then the budget gets scaled against a number that is exactly 100% wrong.

Generate the event ID once, in the data layer, and pass the same value to both. Not two random IDs. Not a timestamp. The same string.

### 2. Consent state doesn't travel

The server doesn't know what the user consented to unless you tell it. Consent lives in the browser, and it must be forwarded explicitly with every event.

A server that never receives a denial will process the event, because from its point of view nothing said no. Your GDPR obligation didn't move to the server. Only your code did. This is exactly the trap I described in the [cookieless tracking article](https://piotrlitwa.com/articles/en/cookieless-tracking.html): server-side isn't a consent workaround, and regulators haven't been persuaded otherwise.

### 3. Client ID continuity

When you cut over, the server starts setting the cookie. If it generates a new client ID instead of reading the existing `_ga` cookie, every returning user becomes a new user on cutover day.

You will see a huge spike in new users, a collapse in returning users, and broken attribution for anyone mid-journey. Read the existing cookie. Do not mint a new one.

### 4. Ecommerce items arrays get flattened

The `items` array is the most fragile part of any ecommerce payload. It survives the browser fine and then arrives at the server as a string, or with the wrong nesting, and GA4 silently drops it.

Symptom: purchase events arrive, revenue arrives, and item-level reporting is empty. Everything looks fine on the dashboard everyone watches, and the merchandising reports quietly go blank.

### 5. Cross-domain and redirect chains

If your checkout is on another domain, the linker parameters have to survive the hop. Server-side changes where the cookie is written, and a cross-domain setup that worked before can stop working after.

## Phase 4: Cutover, one tag at a time

When the numbers reconcile, migrate **one tag at a time**, in this order:

1. **GA4** first. Highest volume, most tolerant of error, easiest to compare.
2. **Meta CAPI** second. Highest risk, because of the deduplication problem above. Watch it for a week.
3. **Google Ads** third.
4. **Everything else** last.

After each tag: watch for 48 hours before moving to the next one. If you migrate all four in one afternoon and conversions drop, you have four suspects and no way to isolate the cause. Migrate one, and you have one.

Keep the shadow property running for a month after full cutover. It costs nothing and it's your only reference point if someone asks in three weeks whether the numbers changed.

### The rollback plan

Write it down before you start, because you won't want to write it at 18:00 on the day something breaks.

Rollback for a GTM migration is genuinely simple, and this is the underrated advantage of doing this in GTM at all: **revert the container to the previous published version**. Two clicks. Your tags go back to client-side and data flows exactly as it did before.

That's only true if you didn't simultaneously rip out the client-side tags. Leave them in place, paused, for a month. Deleting them on cutover day is the one decision that turns a two-click rollback into an incident.

## How long this actually takes

For a mid-size e-commerce site with GA4, Meta CAPI, and Google Ads:

- **Pre-flight checks and fixes**: 2-5 days, longer if the audit finds real problems.
- **Server setup and subdomain**: 1-2 days.
- **Parallel run**: 2 weeks minimum, and this isn't the part to compress.
- **Reconciliation and fixing gaps**: 3-7 days.
- **Staged cutover**: 2 weeks, at one tag per 48 hours plus buffer.

Call it **6 to 8 weeks** end to end, of which most is waiting and watching rather than building. Anyone promising a server-side migration in a week is either skipping the parallel run or has never had to explain a 40% revenue drop to a CFO.

## Frequently asked questions

**How long does a server-side tagging migration take?**
Six to eight weeks for a typical mid-size e-commerce setup. Most of that is the two-week parallel run and the staged cutover, both of which exist to catch problems before they reach production data.

**Do I have to migrate everything at once?**
No, and you should not. Migrate one tag at a time, starting with GA4 and waiting 48 hours between each. If something breaks after a single-tag migration, you know exactly what caused it.

**Why is my server-side data higher than client-side?**
Because ad blockers no longer block it. A 2-8% increase in events is the expected and desirable outcome. A much larger jump usually means an event is firing twice.

**Will server-side tagging break my Meta pixel?**
It will double-count your conversions unless you implement deduplication correctly. The browser event and the server event must carry the same `event_id`, generated once and passed to both. This is the most common and most expensive migration bug.

**Does server-side tagging remove the need for consent?**
No. Consent must be forwarded explicitly from the browser to the server. A server that receives no consent signal will process events anyway, which is a GDPR violation, not a feature.

**How do I roll back if the migration goes wrong?**
Revert the GTM container to the previously published version. This works only if you left the client-side tags in place (paused, not deleted). Keep them for a month after cutover.

## Next steps

The migration itself isn't technically hard. The discipline is hard: standing up a server and not immediately pointing production traffic at it, running two systems in parallel for two weeks when everyone wants it done by Friday, and cutting over one tag at a time when four would be faster.

Start with the pre-flight checks, because they determine whether this is a three-week project or a three-month one. If your client-side tracking already disagrees with your backend, fix that first. Migrating a broken setup produces a broken setup that is harder to debug and more expensive to host.

Then build the shadow property and be patient. The parallel run is the whole method. Everything else is plumbing.

If you want the pre-flight checks run properly, the [Free GTM Audit](https://piotrlitwa.com/checkGTM/) covers the container side. And if you want the migration scoped and implemented, that is [custom development work](https://piotrlitwa.com/services.html#custom): €80/hour, or a fixed price after a 30-minute scoping call. I have done this across WordPress, WooCommerce, Shoper, PrestaShop, and custom stacks. [Full pricing here](https://piotrlitwa.com/pricing.html).

---

**Sources and further reading**
- [Server-side tagging documentation](https://developers.google.com/tag-platform/tag-manager/server-side) (Google)
- [Meta Conversions API deduplication](https://developers.facebook.com/docs/marketing-api/conversions-api/deduplicate-pixel-and-server-events) (Meta)
- [Consent Mode with server-side tagging](https://developers.google.com/tag-platform/security/guides/consent) (Google)

---

*Written by [Piotr Litwa](https://piotrlitwa.com/about.html), independent GTM & Analytics specialist.*
