Server-Side GTM + Google Ads Setup Guide

I see broken server-side GTM setups for Google Ads in about 60% of accounts I audit. The most common mistake? People think they can just flip the switch to server-side and everything magically works. It doesn’t. You need proper container architecture, correct API endpoints, and field mapping that actually matches what Google expects.

The data quality improvement is worth it though — I typically see 15-25% more conversion data when server-side is configured correctly.

What You’ll Have Working By The End

Prerequisites

Step 1: Deploy Your Server-Side GTM Container

First, you need somewhere to run your server container. Google Cloud Run is the easiest option.

In Google Cloud Console:

  1. Enable the Cloud Run API
  2. Go to GTM → Admin → Container Settings → Server Container
  3. Click “Automatically provision tagging server”
  4. Select your GCP project and region
  5. Choose “Cloud Run” as deployment target

This gives you a tagging server URL like https://your-container-abcdef.a.run.app

For custom hosting, you’ll deploy the GTM server container image:

docker run -d -p 8080:8080 \
  -e CONTAINER_CONFIG="your-container-config-string" \
  gcr.io/cloud-tagging-10302018/gtm-cloud-image

The container config string comes from your server container settings in GTM.

Step 2: Configure Your Web Container to Send to Server

In your web GTM container, you need to route events to your server instead of directly to Google Ads.

Create a new Google Ads Conversion Tracking tag:

The transport URL tells the web container to send data to your server instead of directly to Google.

Do this for ALL your Google Ads tags — conversion tracking, remarketing, enhanced conversions.

Step 3: Set Up Server-Side Tags

In your server container, create tags that receive web data and forward it to Google Ads.

Create a Google Ads Conversion Tracking tag (server-side version):

The server tag receives the event from your web container and forwards it to Google using the Measurement Protocol.

For enhanced conversions, add these fields to your server tag:

// User-provided data fields
email: {{Email}}, // Variable pulling from customer data
phone: {{Phone}},
first_name: {{First Name}},
last_name: {{Last Name}},
street: {{Street Address}}

Step 4: Configure Data Layer and Customer Data

Server-side enhanced conversions need customer data. You have two options:

Option 1: Send from web container Push customer data to the data layer on conversion pages:

dataLayer.push({
  'event': 'purchase',
  'transaction_id': 'T12345',
  'value': 99.99,
  'currency': 'USD',
  'customer_data': {
    'email': 'customer@example.com',
    'phone': '+1234567890',
    'first_name': 'John',
    'last_name': 'Smith'
  }
});

Option 2: Enrich server-side (preferred) Use a Custom Tag template or webhook to pull customer data based on transaction ID:

// In your server container custom tag
const transactionId = data.transaction_id;
const customerData = await fetchCustomerData(transactionId);

// Send to Google Ads with enriched data
const enrichedEvent = {
  ...originalEvent,
  user_data: {
    email_address: sha256(customerData.email),
    phone_number: sha256(customerData.phone),
    first_name: sha256(customerData.first_name),
    last_name: sha256(customerData.last_name)
  }
};

I prefer option 2 because it keeps PII off the client-side and ensures data consistency.

Step 5: Handle Cross-Domain Tracking

If you have multiple domains, configure your server container to handle cross-domain client IDs.

In your server container, create a Client ID variable that checks for:

  1. _ga cookie value
  2. client_id URL parameter
  3. Fallback to generated UUID
// Server-side Client ID logic
function getClientId() {
  const gaCookie = getCookieValues('_ga')[0];
  if (gaCookie) {
    return gaCookie.split('.').slice(-2).join('.');
  }
  
  const urlClientId = getRequestQueryParameter('client_id');
  if (urlClientId) {
    return urlClientId;
  }
  
  return generateRandom(1000000000, 9999999999) + '.' + Math.floor(Date.now() / 1000);
}

Set this Client ID in all your Google Ads server tags to maintain user identity across domains.

Testing & Verification

Server Container Debug:

  1. In GTM server container, enable Preview mode
  2. Send a test conversion from your website
  3. Verify the event appears in server Preview with correct data structure
  4. Check that your Google Ads server tag fires successfully

Google Ads Verification:

  1. Go to Google Ads → Tools → Conversions
  2. Check “Recent conversions” — server-side conversions should appear within 2-4 hours
  3. Verify conversion values and attribution match expected data
  4. For enhanced conversions, check the “Enhancement” column shows “Yes”

Data Quality Check: Compare conversion counts between:

Acceptable variance is 5-15%. Higher variance indicates data loss or double-counting.

Enhanced Conversions Debug: Use Google Ads conversion tracking tag helper or check the Network tab for successful POST requests to https://googleadservices.com/conversion/ with em, ph, fn, ln parameters present and properly hashed.

Troubleshooting

Problem: Server container shows successful tag fires but no conversions in Google Ads Solution: Check your Measurement Protocol payload format. Google Ads requires specific field names (tid for conversion ID, t for transaction type). Use the Google Ads server tag template, don’t build custom Measurement Protocol calls.

Problem: Enhanced conversions showing “No” in Google Ads despite sending customer data Solution: Verify customer data is properly hashed with SHA256 and formatted correctly. Email should be lowercase, phone numbers should include country code. Use GTM’s built-in hashing functions, not custom JavaScript.

Problem: High data variance (>20%) between server logs and Google Ads reporting Solution: Check for duplicate firing. If your web container still has direct Google Ads tags AND server tags, you’re double-sending. Remove direct tags from web container once server-side is working.

Problem: Cross-domain conversions not attributing correctly Solution: Ensure your Client ID logic is consistent across all domains. Use the same _ga cookie format and pass client_id in cross-domain links. Check that your server container is using the unified Client ID for all Google Ads events.

Problem: Server container timing out or returning 500 errors Solution: Check your hosting resource limits. Cloud Run default timeout is 300 seconds — increase if you’re doing heavy data enrichment. For custom hosting, ensure sufficient CPU/memory allocation.

Problem: Conversion values showing as 0 in Google Ads despite sending correct values Solution: Verify your currency format matches Google Ads expectations. Use 3-letter currency codes (USD, not $) and decimal values (99.99, not $99.99). Check that your value variable in server container is numeric, not string.

What To Do Next

Once your server-side setup is working, you can expand to other platforms and add more sophisticated data enrichment:

Having issues with your server-side tracking setup? I audit tracking implementations daily and can spot configuration issues fast. Get a free tracking audit — I’ll review your server container and identify exactly what’s broken.

This guide is part of the Complete Server-Side GTM Hub — your resource for moving beyond client-side tracking limitations.