Skip to content

Deploy

All Terraform files are in the terraform/ directory. Clone the repository and deploy directly:

Terminal window
git clone https://github.com/f5xc-salesdemos/cdn-simulator.git
cd cdn-simulator/terraform
cp terraform.tfvars.example terraform.tfvars
# Edit terraform.tfvars with your Azure subscription ID and origin server

The terraform directory contains 9 files following the Demo Resource Standard:

  • versions.tf — Terraform and provider version constraints (azurerm ~> 4.0, azuread ~> 3.0)
  • providers.tf — Azure RM and Azure AD provider configuration
  • data.tf — Azure AD data sources for deployer auto-resolution
  • locals.tf — Deployer resolution, Azure Cloud Adoption Framework resource naming, standard tags
  • main.tf — Resource group (named rg-cdn-simulator-{environment}-{deployer})
  • variables.tf — All input variables (3 required, 8 optional)
  • network.tf — VNet (10.100.0.0/16), subnet, public IP, NSG (ports 22/80/443), NIC
  • vm.tf — Ubuntu 24.04 VM with cloud-init via templatefile()
  • outputs.tf — 17 outputs (15 standard + 2 component-specific)

variables.tf defines 11 input variables organized into General, Compute, and Component-Specific sections. The deployer identifier is auto-resolved from your Azure AD account — you only need to set subscription_id, origin_server, and origin_host:

# ---------------------------------------------------------
# General
# ---------------------------------------------------------
variable "subscription_id" {
description = "Azure subscription ID"
type = string
}
variable "deployer" {
description = "Override for deployer identifier (auto-resolved from Azure AD if empty). Required for service principal or managed identity authentication."
type = string
default = ""
}
variable "location" {
description = "Azure region for all resources"
type = string
default = "eastus2"
}
variable "environment" {
description = "Environment label used in resource group naming and tags"
type = string
default = "lab"
}
variable "tags" {
description = "Additional tags merged with standard tags (component, environment, deployer, managed_by)"
type = map(string)
default = {}
}
# ---------------------------------------------------------
# Compute
# ---------------------------------------------------------
variable "vm_size" {
description = "Azure VM size — F-series compute-optimized recommended (F4s_v2 for lab, F16s_v2 for load testing, F32s_v2 for production)"
type = string
default = "Standard_F4s_v2"
}
variable "admin_username" {
description = "SSH admin username for the VM"
type = string
default = "azureuser"
}
variable "ssh_public_key_path" {
description = "Path to the SSH public key file"
type = string
default = "~/.ssh/id_ed25519.pub"
}
variable "disk_size_gb" {
description = "OS disk size in GB"
type = number
default = 30
}
# ---------------------------------------------------------
# Component-Specific
# ---------------------------------------------------------
variable "origin_server" {
description = "Origin server URL for cache miss forwarding (e.g., an HTTPS VIP or a direct HTTP origin IP)"
type = string
}
variable "origin_host" {
description = "Origin server host:port for NGINX upstream (no scheme). Use IP:443 for HTTPS or IP:80 for HTTP."
type = string
}

network.tf creates the VNet, subnet, public IP, NSG (ports 22/80/443), and NIC:

resource "azurerm_virtual_network" "main" {
name = local.name.virtual_network
address_space = ["10.100.0.0/16"]
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
tags = azurerm_resource_group.main.tags
}
resource "azurerm_subnet" "main" {
name = local.name.subnet
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = ["10.100.1.0/24"]
}
resource "azurerm_public_ip" "main" {
name = local.name.public_ip
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
allocation_method = "Static"
sku = "Standard"
tags = azurerm_resource_group.main.tags
}
resource "azurerm_network_security_group" "main" {
name = local.name.nsg
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
security_rule {
name = "AllowHTTP"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "80"
source_address_prefix = "*"
destination_address_prefix = "*"
}
security_rule {
name = "AllowHTTPS"
priority = 110
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "443"
source_address_prefix = "*"
destination_address_prefix = "*"
}
security_rule {
name = "AllowSSH"
priority = 120
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "*"
destination_address_prefix = "*"
}
tags = azurerm_resource_group.main.tags
}
resource "azurerm_network_interface" "main" {
name = local.name.network_interface
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.main.id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.main.id
}
tags = azurerm_resource_group.main.tags
}
resource "azurerm_network_interface_security_group_association" "main" {
network_interface_id = azurerm_network_interface.main.id
network_security_group_id = azurerm_network_security_group.main.id
}

vm.tf creates the Ubuntu 24.04 VM. The SSH public key path is expanded via pathexpand() to handle ~. Cloud-init template receives origin_server and origin_host variables:

resource "azurerm_linux_virtual_machine" "main" {
name = local.name.virtual_machine
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
size = var.vm_size
admin_username = var.admin_username
disable_password_authentication = true
admin_ssh_key {
username = var.admin_username
public_key = file(pathexpand(var.ssh_public_key_path))
}
network_interface_ids = [azurerm_network_interface.main.id]
os_disk {
caching = "ReadWrite"
storage_account_type = "Premium_LRS"
disk_size_gb = var.disk_size_gb
}
source_image_reference {
publisher = "Canonical"
offer = "ubuntu-24_04-lts"
sku = "server"
version = "latest"
}
custom_data = base64encode(templatefile("${path.module}/cloud-init.yaml", {
origin_server = var.origin_server
origin_host = var.origin_host
}))
boot_diagnostics {}
tags = azurerm_resource_group.main.tags
}

cloud-init.yaml provisions the VM with kernel tuning, systemd limits, NGINX with performance-optimized configuration, 128 MB cache keys zone, upstream keepalive pool, gzip compression, and 67+ CDN vendor headers. A shared helper library (/usr/local/lib/cloud-init-helpers.sh) provides retry logic and progress logging to /var/log/cloud-init-progress.log.

The cloud-init uses Terraform template variables: ${origin_server} and ${origin_host} for the upstream configuration. NGINX variables like ${request_id} are escaped as $${request_id} in the Terraform templatefile.

#cloud-config
package_update: true
package_upgrade: true
bootcmd:
- mkdir -p /var/cache/nginx/cdn
- chown www-data:www-data /var/cache/nginx/cdn 2>/dev/null || true
packages:
- nginx
- irqbalance
write_files:
# ── Kernel tuning ──────────────────────────────────────────────
- path: /etc/sysctl.d/99-cdn-tuning.conf
content: |
net.core.somaxconn = 262144
net.core.netdev_max_backlog = 262144
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.tcp_tw_reuse = 1
net.ipv4.ip_local_port_range = 1024 65535
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_keepalive_intvl = 15
net.ipv4.tcp_keepalive_probes = 5
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_max_tw_buckets = 8000000
fs.file-max = 8388608
vm.swappiness = 10
# ── Systemd override for NGINX file descriptor limits ──────────
- path: /etc/systemd/system/nginx.service.d/override.conf
content: |
[Service]
LimitNOFILE=262144
LimitNPROC=262144
# ── OS-level limits for www-data (NGINX worker user) ───────────
- path: /etc/security/limits.d/99-nginx.conf
content: |
www-data soft nofile 262144
www-data hard nofile 262144
# ── NGINX main config ──────────────────────────────────────────
- path: /etc/nginx/nginx.conf
content: |
user www-data;
worker_processes auto;
worker_rlimit_nofile 262144;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log;
include /etc/nginx/modules-enabled/*.conf;
events {
use epoll;
worker_connections 32768;
multi_accept on;
accept_mutex off;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
types_hash_max_size 2048;
server_tokens off;
client_max_body_size 50m;
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format cdn '$remote_addr [$time_local] "$request" $status $body_bytes_sent $upstream_cache_status $request_time';
access_log /var/log/nginx/access.log cdn;
keepalive_timeout 65;
keepalive_requests 100000;
proxy_buffering on;
proxy_buffer_size 16k;
proxy_buffers 128 16k;
proxy_busy_buffers_size 256k;
gzip on;
gzip_comp_level 4;
gzip_min_length 256;
gzip_vary on;
gzip_proxied any;
gzip_types text/plain text/css text/javascript text/xml
application/json application/javascript application/xml
application/xml+rss application/atom+xml
application/ld+json application/manifest+json
image/svg+xml;
open_file_cache max=200000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
include /etc/nginx/conf.d/*.conf;
}
# ── CDN edge proxy config ──────────────────────────────────────
- path: /etc/nginx/conf.d/cdn-edge.conf
content: |
proxy_cache_path /var/cache/nginx/cdn
levels=1:2
keys_zone=cdn_cache:128m
max_size=25g
inactive=24h
use_temp_path=off;
upstream origin_backend {
server ${origin_host};
keepalive 1024;
keepalive_timeout 60s;
keepalive_requests 100000;
}
map $request_id $cdn_ray_id {
default "$${request_id}-SJC";
}
map $request_id $cdn_azure_ref {
default "0$${request_id}AAAAAA";
}
map $request_id $cdn_amz_cf_id {
default "E1$${request_id}==";
}
map $http_user_agent $is_mobile {
default "false";
"~*Mobile|Android|iPhone|iPod|BlackBerry|Opera Mini|IEMobile" "true";
}
map $http_user_agent $is_tablet {
default "false";
"~*iPad|Android(?!.*Mobile)|Tablet|Kindle|PlayBook" "true";
}
map $http_user_agent $is_desktop {
default "true";
"~*Mobile|Android|iPhone|iPod|BlackBerry|Opera Mini|IEMobile|iPad|Tablet|Kindle|PlayBook" "false";
}
server {
listen 80 reuseport;
server_name _;
location /health {
access_log off;
return 200 '{"status":"healthy","component":"cdn-edge","engine":"nginx","vendor_profiles":["akamai","cloudflare","cloudfront","fastly","azure-front-door"]}';
add_header Content-Type application/json;
}
location / {
proxy_pass https://origin_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_ssl_server_name on;
proxy_ssl_name csd.bankexample.com;
proxy_ssl_verify off;
proxy_read_timeout 180s;
proxy_connect_timeout 10s;
proxy_send_timeout 15s;
# Standard
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Via "1.1 cdn-simulator";
proxy_set_header Forwarded "for=$remote_addr;proto=$scheme;host=$host";
proxy_set_header CDN-Loop "cdn-simulator";
# Akamai
proxy_set_header True-Client-IP $remote_addr;
proxy_set_header X-Akamai-Edgescape "georegion=263,country_code=US,region_code=CA,city=SANJOSE,dma=807,pmsa=7400,msa=7362,areacode=408,county=SANTACLARA,fips=06085,lat=37.3353,long=-121.8938,timezone=PST,zip=95113-95196,continent=NA,throughput=vhigh,bw=5000,network=att.net,asnum=7018,network_type=broadband";
proxy_set_header X-Akamai-Device-Characteristics "brand_name=Generic;model_name=Browser;is_mobile=$is_mobile;is_tablet=$is_tablet;is_wireless_device=$is_mobile;device_os=Linux;device_os_version=1.0;resolution_width=1920;resolution_height=1080";
proxy_set_header X-Akamai-Request-ID $request_id;
# Cloudflare
proxy_set_header CF-Connecting-IP $remote_addr;
proxy_set_header CF-IPCountry "US";
proxy_set_header cf-ipcity "San Jose";
proxy_set_header cf-ipcontinent "NA";
proxy_set_header cf-iplatitude "37.3353";
proxy_set_header cf-iplongitude "-121.8938";
proxy_set_header cf-region "California";
proxy_set_header cf-region-code "CA";
proxy_set_header cf-metro-code "807";
proxy_set_header cf-postal-code "95113";
proxy_set_header cf-timezone "America/Los_Angeles";
proxy_set_header Cf-Ray $cdn_ray_id;
proxy_set_header CF-Visitor '{"scheme":"https"}';
proxy_set_header cf-bot-score "85";
proxy_set_header cf-verified-bot "false";
proxy_set_header cf-ja3-hash "e7d705a3286e19ea42f587b344ee6865";
proxy_set_header cf-ja4 "t13d1516h2_8daaf6152771_b0da82dd1658";
# CloudFront
proxy_set_header CloudFront-Viewer-Address "$remote_addr:$remote_port";
proxy_set_header CloudFront-Viewer-Country "US";
proxy_set_header CloudFront-Viewer-Country-Name "United States";
proxy_set_header CloudFront-Viewer-Country-Region "CA";
proxy_set_header CloudFront-Viewer-Country-Region-Name "California";
proxy_set_header CloudFront-Viewer-City "San Jose";
proxy_set_header CloudFront-Viewer-Postal-Code "95113";
proxy_set_header CloudFront-Viewer-Latitude "37.33530";
proxy_set_header CloudFront-Viewer-Longitude "-121.89300";
proxy_set_header CloudFront-Viewer-Time-Zone "America/Los_Angeles";
proxy_set_header CloudFront-Viewer-Metro-Code "807";
proxy_set_header CloudFront-Viewer-ASN "7018";
proxy_set_header CloudFront-Viewer-Http-Version "2.0";
proxy_set_header CloudFront-Forwarded-Proto "https";
proxy_set_header CloudFront-Viewer-TLS "TLSv1.3:TLS_AES_128_GCM_SHA256:sessionResumed";
proxy_set_header CloudFront-Viewer-JA3-Fingerprint "e7d705a3286e19ea42f587b344ee6865";
proxy_set_header CloudFront-Is-Desktop-Viewer $is_desktop;
proxy_set_header CloudFront-Is-Mobile-Viewer $is_mobile;
proxy_set_header CloudFront-Is-Tablet-Viewer $is_tablet;
proxy_set_header CloudFront-Is-SmartTV-Viewer "false";
proxy_set_header X-Amz-Cf-Id $cdn_amz_cf_id;
# Fastly
proxy_set_header Fastly-Client-IP $remote_addr;
proxy_set_header Fastly-SSL "1";
proxy_set_header Fastly-Client "1";
proxy_set_header Fastly-FF "cache-sjc3120-SJC";
proxy_set_header X-Geo-Country-Code "US";
proxy_set_header X-Geo-Country-Code3 "USA";
proxy_set_header X-Geo-Country-Name "United States";
proxy_set_header X-Geo-City "San Jose";
proxy_set_header X-Geo-Region "CA";
proxy_set_header X-Geo-Continent-Code "NA";
proxy_set_header X-Geo-Latitude "37.3353";
proxy_set_header X-Geo-Longitude "-121.8938";
proxy_set_header X-Geo-Postal-Code "95113";
proxy_set_header X-Geo-Metro-Code "807";
proxy_set_header X-Geo-ASN "7018";
proxy_set_header X-Geo-Conn-Speed "broadband";
proxy_set_header X-Geo-Conn-Type "wired";
# Azure Front Door
proxy_set_header X-Azure-ClientIP $remote_addr;
proxy_set_header X-Azure-SocketIP $remote_addr;
proxy_set_header X-Azure-Ref $cdn_azure_ref;
proxy_set_header X-Azure-FDID "a0a0a0a0-bbbb-cccc-dddd-e1e1e1e1e1e1";
proxy_set_header X-Azure-RequestChain "hops=1";
# Cache
proxy_set_header Host csd.bankexample.com;
proxy_cache cdn_cache;
proxy_cache_methods GET HEAD;
proxy_cache_valid 200 301 302 4h;
proxy_cache_valid 404 1m;
proxy_cache_key "$scheme$host$request_uri";
proxy_cache_lock on;
proxy_cache_lock_age 3s;
proxy_cache_lock_timeout 3s;
proxy_cache_background_update on;
proxy_cache_use_stale updating error timeout http_500 http_502 http_503 http_504;
proxy_ignore_headers Set-Cookie Cache-Control Expires Vary;
proxy_hide_header X-Cache-Status;
proxy_hide_header Vary;
add_header X-Cache-Status $upstream_cache_status always;
add_header X-CDN-Edge "cdn-simulator" always;
add_header X-CDN-POP "SJC" always;
add_header X-Served-By "cache-sjc3120-SJC" always;
add_header X-Request-ID $request_id always;
}
}
- path: /etc/nginx/conf.d/default.conf
content: ""
- path: /usr/local/lib/cloud-init-helpers.sh
permissions: "0644"
content: |
#!/bin/sh
PROGRESS_LOG="/var/log/cloud-init-progress.log"
log_phase() {
_phase="$1"; shift
_msg="$${*:-started}"
_ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
printf '[%s] [%s] %s\n' "$_ts" "$_phase" "$_msg" | tee -a "$PROGRESS_LOG" >&2
}
retry_cmd() {
_max="$1"; _base="$2"; shift 2
_attempt=1
while [ "$_attempt" -le "$_max" ]; do
if "$@"; then return 0; fi
if [ "$_attempt" -lt "$_max" ]; then
_wait=$(( _base * _attempt ))
log_phase "retry" "attempt $_attempt/$_max failed ($1) — retrying in $${_wait}s"
sleep "$_wait"
fi
_attempt=$(( _attempt + 1 ))
done
log_phase "retry" "FAILED after $_max attempts: $1"
return 1
}
runcmd:
- |
. /usr/local/lib/cloud-init-helpers.sh
log_phase "init" "cdn-simulator provisioning started"
- sysctl -p /etc/sysctl.d/99-cdn-tuning.conf || exit 1
- systemctl daemon-reload
- rm -f /etc/nginx/sites-enabled/default
- chown -R www-data:www-data /var/cache/nginx/cdn
- nginx -t || exit 1
- systemctl enable nginx
- systemctl restart nginx
- systemctl enable irqbalance
- systemctl start irqbalance
- |
. /usr/local/lib/cloud-init-helpers.sh
NIC=$(ip -o link show | awk -F': ' '/state UP/{print $2}' | grep -v lo | head -1)
if [ -n "$NIC" ]; then
log_phase "nic" "configuring RPS/RFS for $NIC"
echo 65536 > /proc/sys/net/core/rps_sock_flow_entries 2>/dev/null || true
for i in $(seq 0 $(($(nproc)-1))); do
echo 8192 > /sys/class/net/$NIC/queues/rx-$i/rps_flow_cnt 2>/dev/null || true
done
else
log_phase "nic" "no active NIC found — skipping RPS/RFS"
fi
- |
. /usr/local/lib/cloud-init-helpers.sh
log_phase "complete" "cdn-simulator provisioned"

outputs.tf exposes 17 outputs following the Demo Resource Standard — 15 standard outputs shared by all demo resources (deployer, public_ip, private_ip, ssh_command, resource_group_name, vm_name, nsg_name, vnet_name, subnet_id, component, environment, resource_group_id, vm_id, nsg_id, location) plus 2 component-specific outputs (edge_url, health_check_url):

# ---------------------------------------------------------
# Standard Outputs (present in every demo resource)
# ---------------------------------------------------------
output "deployer" {
description = "Resolved deployer identifier"
value = local.deployer
}
output "resource_group_name" {
description = "Name of the resource group"
value = azurerm_resource_group.main.name
}
output "resource_group_id" {
description = "Resource ID of the resource group"
value = azurerm_resource_group.main.id
}
output "location" {
description = "Azure region"
value = azurerm_resource_group.main.location
}
output "public_ip" {
description = "Public IP address of the VM"
value = azurerm_public_ip.main.ip_address
}
output "private_ip" {
description = "Private IP address of the VM"
value = azurerm_network_interface.main.private_ip_address
}
output "ssh_command" {
description = "SSH command to connect to the VM"
value = "ssh ${var.admin_username}@${azurerm_public_ip.main.ip_address}"
}
output "vm_name" {
description = "Name of the virtual machine"
value = azurerm_linux_virtual_machine.main.name
}
output "vm_id" {
description = "Resource ID of the virtual machine"
value = azurerm_linux_virtual_machine.main.id
}
output "nsg_name" {
description = "Name of the network security group"
value = azurerm_network_security_group.main.name
}
output "nsg_id" {
description = "Resource ID of the network security group"
value = azurerm_network_security_group.main.id
}
output "vnet_name" {
description = "Name of the virtual network"
value = azurerm_virtual_network.main.name
}
output "subnet_id" {
description = "Resource ID of the subnet"
value = azurerm_subnet.main.id
}
output "component" {
description = "Component name"
value = local.component
}
output "environment" {
description = "Environment label"
value = var.environment
}
# ---------------------------------------------------------
# Component-Specific Outputs
# ---------------------------------------------------------
output "edge_url" {
description = "HTTP URL of the CDN edge node"
value = "http://${azurerm_public_ip.main.ip_address}"
}
output "health_check_url" {
description = "Health check endpoint"
value = "http://${azurerm_public_ip.main.ip_address}/health"
}

Copy terraform.tfvars.example to terraform.tfvars and fill in your values. The .gitignore excludes terraform.tfvars to prevent committing credentials:

# Copy this file to terraform.tfvars and fill in your values.
# terraform.tfvars is gitignored — never commit real credentials.
# --- Required ---
subscription_id = "00000000-0000-0000-0000-000000000000"
origin_server = "http://your-origin-ip"
origin_host = "your-origin-ip:80"
# --- Optional overrides (defaults shown) ---
# deployer = "" # auto-resolved from Azure AD
# location = "eastus2"
# environment = "lab"
# vm_size = "Standard_F4s_v2"
# disk_size_gb = 30
# admin_username = "azureuser"
# ssh_public_key_path = "~/.ssh/id_ed25519.pub"
# tags = {}
Terminal window
# Initialize Terraform
terraform init
# Review the plan
terraform plan
# Apply
terraform apply

Terraform outputs the public IP, SSH command, and edge URL after successful deployment.

The F-series compute-optimized VMs are recommended for this CPU-bound NGINX proxy workload. The default Standard_F4s_v2 (4 vCPU, 8 GiB) is suitable for lab and demo use. Override the vm_size variable for load testing or production benchmarking scenarios. Cloud-init kernel tuning and NGINX config scale automatically with vCPU count (worker_processes auto).

After terraform apply completes, allow 2-3 minutes for cloud-init to finish installing and configuring NGINX. Verify the health endpoint:

Terminal window
curl -s "http://$(terraform output -raw public_ip)/health" | jq .

Expected response:

{
"status": "healthy",
"component": "cdn-edge",
"engine": "nginx",
"vendor_profiles": [
"akamai",
"cloudflare",
"cloudfront",
"fastly",
"azure-front-door"
]
}

The CDN simulator requires a deployed origin server. Use the origin server’s terraform outputs to populate the required variables:

Terminal window
cd ../origin-server/terraform
origin_ip=$(terraform output -raw public_ip)
cd ../../cdn-simulator/terraform
cat > terraform.tfvars <<EOF
subscription_id = "your-subscription-id"
origin_server = "http://${origin_ip}"
origin_host = "${origin_ip}:80"
EOF
Required VariableSourceFormat
origin_serverOrigin server public_ip outputhttp://<ip> (include scheme)
origin_hostOrigin server public_ip output<ip>:80 (no scheme, include port)

Proceed to NGINX Configuration for customization options or Verify for cache testing.