Insights

Sitecore on Azure PaaS: How We Moved a National Tourism Platform Off VM Scale Sets

How a national tourism platform moved Sitecore, .NET and a React front end from VM Scale Sets to Azure PaaS behind Cloudflare. In one 24-hour window, Cloudflare served 99.2% of 55.8 million requests without reaching the origin.

Muhammad Rafay9 min read
Sitecore on Azure PaaS Architecture
On this page

A national tourism platform needed more than VM Scale Sets could offer. During campaigns, heavy traffic crashed the front end, and manual server tasks delayed releases. I led the shift to Azure PaaS, added Cloudflare and Azure Front Door, secured all data services, and set up each environment with a single Terraform module. Now, campaign traffic is handled automatically, and we release updates several times a week with approval steps. In one day, Cloudflare handled 99.2% of 55.8 million requests without hitting the origin.

This case study is for architects and engineers managing enterprise CMS platforms on Azure. The insights here are useful for Sitecore, Umbraco, Kentico, and Optimizely.

At a glance

Item

Detail

Platform

Multiple public websites on Sitecore and .NET, with Optimizely experimentation and a Next.js front end

My role

Cloud architecture, security, networking, CI/CD and production delivery

Before

Sitecore on VM Scale Sets, React under PM2

After

Azure App Service, Container Apps, SQL Managed Instance, Redis, Key Vault, managed identities, Blob Storage, Service Bus

Edge

Cloudflare, then Azure Front Door

Network

One virtual network per environment, private endpoints, site-to-site VPN

Delivery

Terraform and Azure DevOps across development, UAT and production

Edge result

99.2% of 55.8M requests served at the edge in 24 hours

The starting point

The platform ran several public sites using Sitecore and .NET, sharing both identity and content. Editors published new content daily, and we released updates several times a week.

The platform had two main needs. Public sites needed to handle unpredictable traffic, while legacy on-premises and partner systems required private, secure connections because they couldn't be exposed to the internet.

Our first setup used VM Scale Sets. We managed the servers running Sitecore services, and React ran on those same VMs using PM2, a Node.js process manager. This worked initially, but as traffic increased, the platform became difficult to manage.

What broke

  • The front end crashed during peak times. Tourism traffic surged during campaigns and holidays, overwhelming the PM2 processes and causing outages when demand was highest.
  • The cache crashed too. Nginx failed under the same load. We cleared its cache by hand whenever it served stale content.
  • Sitecore was unreliable at busy times. Content Management and Content Delivery were not consistently available. Editors struggled to publish campaign content, and Content Delivery instability reached the public site.
  • Operations were mostly manual. We had to restart servers ourselves, and IIS application pool problems kept coming back. Troubleshooting required checking each server one by one.
  • Environments became inconsistent. We set up development, UAT, and production manually, and each server stored its configuration in web.config files.
  • Releases sometimes happened out of order. Code deployments and content publishing were separate, so sequence mistakes were common.

Why we moved to PaaS

The bottleneck had changed from compute to operations. Adding VMs would only add more servers to patch, restart, and keep consistent. So we asked a different question: which components should we keep managing ourselves? We handed the rest to Azure.

Area

Before

After

Sitecore hosting

VM Scale Sets, IIS

Content Management, Content Delivery and Identity as separate App Services

Front end

React under PM2 on VMs

React on App Service, Container Apps for containerised workloads

Caching

Nginx, cleared by hand

Cloudflare caching plus incremental static regeneration, where React refreshes a page in the background

Environments

Built by hand

Terraform

Databases

SQL Server

Azure SQL Managed Instance, with geo-redundant backups

Media

Blob Storage

Blob Storage, now reached only through a private endpoint

Secrets

Stored on servers

Key Vault with managed identities

Release and monitoring

Manual deploys, per-VM monitoring

Azure DevOps pipelines with approval gates; Azure Monitor, Application Insights and Log Analytics

The architecture: seven layers

Reference architecture diagram: Cloudflare in front of Azure Front Door, Next.js front end, Sitecore PaaS and .NET services, Azure SQL, Redis, Blob Storage and Key Vault over private endpoints, VPN Gateway to on-premises systems, with Azure DevOps, Terraform and Azure Monitor alongside.
  1. Edge. Cloudflare owns DNS and Transport Layer Security (TLS). It caches static assets and pages, runs a web application firewall (WAF) and bot rules, and puts an access policy in front of the Sitecore authoring and admin paths.
  2. Ingress. Front Door routes by host and path and runs health probes. Front Door accepts traffic only from Cloudflare, and App Services accept traffic only from Front Door. An attacker who finds an origin hostname gets nothing. Application Gateway adds a regional entry point where needed.
  3. Experience. Next.js runs on App Service, with Container Apps for containerized sites. Pages use ISR where content allows. Cloudflare serves repeat requests, which keeps traffic peaks away from the origin.
  4. Content and services. Content Management, Content Delivery, and Identity run as separate App Services, with Solr for search and Redis for sessions. The .NET APIs and Optimizely components run alongside them, in containers where that made deployment simpler. API Management controls how those APIs are exposed. Content Delivery serves public traffic, so visitors never reach the authoring service.
  5. Data. SQL Managed Instance holds the Sitecore and application databases, with geo-redundant backups. Redis handles sessions and output cache. A Storage Account holds the media library. Key Vault holds secrets and certificates, which applications read through managed identities.
  6. Network: Each environment uses its own virtual network, with a subnet for each tier and network security groups in between. SQL, Redis, Storage, and Key Vault all use private endpoints, so nothing is exposed publicly. Outbound traffic goes through NAT, and private DNS keeps hostnames consistent.
  7. Delivery and operations: Azure DevOps handles builds and deployments. Terraform defines the infrastructure and Cloudflare settings. Azure Monitor, Application Insights, and Log Analytics track metrics, logs, traces, and alerts.

The edge in practice

Cloudflare Security analytics for the last 24 hours: 55.8M total requests, 84.85k mitigated, 55.35M served by Cloudflare and 363.11k served by the origin

Cloudflare Security analytics for one 24 hour window. Cloudflare served 55.35M requests. Only 363.11k reached the origin.

This is what the edge layer buys. On the VM platform every spike landed on PM2. Here, the origin sees well under one percent of the traffic, and the spike visible at the start of the window never reached Azure.

Security controls

Security is built into every layer rather than added at the end:

  • Cloudflare runs the WAF and bot rules for all public traffic.
  • Sitecore authoring sits behind an access policy. A password alone is never enough.
  • No data service has a public endpoint, and no secret lives on a server.
  • Legacy systems are reached over a site-to-site VPN, so no new internet-facing port was opened.
  • Every infrastructure change goes through a pull request and a pipeline.
  • Key Vault holds every certificate and alerts 60 days before expiry.

Five design decisions and their trade-offs

Decision

Why

Trade-off

Move to PaaS instead of adding VMs

The bottleneck was operations, not compute

We stopped managing hosts and focused on network, security, and release design

Keep Cloudflare in front of Front Door

The organisation already ran DNS, WAF and bot rules in Cloudflare and the security team knew it. Front Door still provides Azure-native routing, health probes and origin control

Two layers overlap on CDN, WAF and TLS. A strict traffic rule keeps them aligned

Private endpoints for every data service

A leaked connection string is useless outside the network, and security reviews became much shorter

Private DNS zones must link to every network that resolves the names. Build agents need a private path, and laptops cannot connect without one

Site-to-site VPN for legacy systems

Legacy systems could not touch the internet. An API gateway with an open port was faster but added exposure

Added VPN latency, which was never the bottleneck. Legacy owners changed nothing

One Terraform module for every environment

Hand-built environments differed in small ways that caused production-only failures

We had to import existing resources and fix drift before the module worked

Private endpoints in Terraform

You pay the private networking cost once, because Terraform makes it repeatable. This Storage private endpoint includes the DNS zone group that makes name resolution work:

hcl
1resource "azurerm_private_endpoint" "storage_blob" {
2 name = "pe-storage-blob-${var.environment}"
3 location = var.location
4 resource_group_name = var.resource_group_name
5 subnet_id = var.data_subnet_id
6
7 private_service_connection {
8 name = "psc-storage-blob"
9 private_connection_resource_id = azurerm_storage_account.media.id
10 subresource_names = ["blob"]
11 is_manual_connection = false
12 }
13
14 private_dns_zone_group {
15 name = "blob-dns"
16 private_dns_zone_ids = [azurerm_private_dns_zone.blob.id]
17 }
18}

Without the DNS zone group, applications keep resolving the public hostname and never reach the private address.

Adopting Terraform safely

Development, UAT and production use the same Terraform module with different variable files. Only resource sizes differ. We adopted it in this order:

  1. Start with non-production.
  2. Import what already exists.
  3. Fix drift in code.
  4. Touch production last.

Delivery and releases

Every application has its own pipeline with the same four stages:

  1. Build and test. Compile, run tests, build the package or container image.
  2. Deploy to development. Runs automatically after a successful build.
  3. Deploy to UAT. Runs after an approval.
  4. Deploy to production. Runs after an approval.

Terraform follows the same path. Each change starts as a pull request, the team reviews the plan there, and the pipeline applies it. Nobody applies from a laptop. Build agents run inside the virtual network so they can reach the private services they deploy to.

A production release follows six steps:

  1. Prove it. The change lands in development and UAT first.
  2. Review. For Terraform, the pull request plan is the record of what will change.
  3. Approve. The production stage waits for a manual approval.
  4. Deploy. The pipeline deploys the code, then runs the Sitecore publish stage. That order ends the old mix-ups between code and content.
  5. Verify. Smoke tests run, and Front Door health probes confirm the origins are live.
  6. Watch. Application Insights and alerts cover the first hours.

The aim is not risk-free releases. It is predictable, uneventful ones.

Monitoring

PaaS removes server work. It does not remove the need for monitoring. Application Insights runs on every App Service and traces requests across Next.js, the .NET APIs and Sitecore. Three conditions page someone:

  • Availability tests failing from two regions
  • Server error rate above threshold for five minutes
  • A dependency such as SQL or Redis failing

Everything else goes to a dashboard. Each alert links to a runbook stored next to the Terraform. An alert says something is wrong. The runbook says what to do next.

Results

Cloudflare analytics for one 24 hour window ending 5 October 2026:

Metric

Value

Total requests

55.8M, about 650 per second on average

Served by Cloudflare

55.35M (99.2%)

Reached the origin

363.11k (0.65%)

Mitigated, challenged or blocked

84.85k (0.15%)

  • Cloudflare mitigated 84,850 requests in that window: 69,320 challenged and 15,530 blocked.
  • Campaign traffic no longer depends on hand-managed PM2 processes.
  • Nobody checks IIS application pools or clears Nginx caches by hand.
  • Editors publish through a path that is tested after every release.
  • All three environments come from one Terraform module.
  • No data service has a public endpoint, and Sitecore authoring is never reachable from the open internet.
  • Releases ship several times a week, with approvals before UAT and production.
  • Nobody manages PM2 processes or patches application servers.

Lessons learned

  1. Scaling problems are not always compute problems. When process management, the operating system and scaling are tied to VMs, more machines add more to operate.
  2. Environment consistency matters as much as performance. Infrastructure as code is how you stop drift.
  3. Private networking touches everything. It changes DNS, build agents, developer access, testing and troubleshooting.
  4. Security controls need functional tests. A WAF can be on while the CMS is broken. A private endpoint can exist while DNS is wrong.

If you are planning a Sitecore, Umbraco, Kentico or Optimizely platform on Azure, the enterprise application and CMS deployments page on the main site describes how I run that work.

Sources