Back to Blog
6 min read

Azure Arc-Enabled Servers: Manage Any Server from Azure

Azure Arc extends Azure management to servers running anywhere - on-premises, at the edge, or in other clouds. At Ignite 2021, Microsoft announced new capabilities that make Arc even more powerful for hybrid scenarios.

What is Azure Arc-Enabled Servers?

Arc-enabled servers project your non-Azure machines into Azure Resource Manager, enabling:

  • Unified management: Single pane of glass for all servers
  • Azure Policy: Enforce configurations across hybrid environments
  • Azure Monitor: Centralized monitoring and alerting
  • Microsoft Defender: Security posture across all machines

Onboarding Servers

Single Server Onboarding

Generate onboarding script from Azure portal or CLI:

# Download and run the onboarding script
curl -L https://aka.ms/azcmagent -o install_arc_agent.sh
chmod +x install_arc_agent.sh

# Run with your credentials
./install_arc_agent.sh \
    --tenant-id "your-tenant-id" \
    --subscription-id "your-subscription-id" \
    --resource-group "rg-arc-servers" \
    --location "eastus" \
    --service-principal-id "sp-client-id" \
    --service-principal-secret "sp-secret"

Windows PowerShell:

# Download the agent
Invoke-WebRequest -Uri "https://aka.ms/AzureConnectedMachineAgent" -OutFile "AzureConnectedMachineAgent.msi"

# Install the agent
msiexec /i AzureConnectedMachineAgent.msi /qn

# Connect to Azure
& "$env:ProgramFiles\AzureConnectedMachineAgent\azcmagent.exe" connect `
    --tenant-id "your-tenant-id" `
    --subscription-id "your-subscription-id" `
    --resource-group "rg-arc-servers" `
    --location "eastus" `
    --service-principal-id "sp-client-id" `
    --service-principal-secret "sp-secret"

At-Scale Onboarding

Use automation for multiple servers:

# generate-onboarding-script.ps1
param(
    [string]$TenantId,
    [string]$SubscriptionId,
    [string]$ResourceGroup,
    [string]$Location,
    [string]$ServicePrincipalId,
    [string]$ServicePrincipalSecret,
    [string[]]$ServerList
)

# Create service principal for onboarding
$sp = az ad sp create-for-rbac `
    --name "arc-onboarding-sp" `
    --role "Azure Connected Machine Onboarding" `
    --scopes "/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroup"

$spInfo = $sp | ConvertFrom-Json

foreach ($server in $ServerList) {
    Write-Host "Onboarding $server..."

    Invoke-Command -ComputerName $server -ScriptBlock {
        param($TenantId, $SubscriptionId, $ResourceGroup, $Location, $SpId, $SpSecret)

        # Download and install agent
        Invoke-WebRequest -Uri "https://aka.ms/AzureConnectedMachineAgent" -OutFile "$env:TEMP\AzureConnectedMachineAgent.msi"
        msiexec /i "$env:TEMP\AzureConnectedMachineAgent.msi" /qn /l*v "$env:TEMP\arc_install.log"

        # Wait for installation
        Start-Sleep -Seconds 30

        # Connect to Azure
        & "$env:ProgramFiles\AzureConnectedMachineAgent\azcmagent.exe" connect `
            --tenant-id $TenantId `
            --subscription-id $SubscriptionId `
            --resource-group $ResourceGroup `
            --location $Location `
            --service-principal-id $SpId `
            --service-principal-secret $SpSecret

    } -ArgumentList $TenantId, $SubscriptionId, $ResourceGroup, $Location, $spInfo.appId, $spInfo.password
}

Managing Arc Servers

Using Azure CLI

# List all Arc-enabled servers
az connectedmachine list \
    --resource-group rg-arc-servers \
    --output table

# Get detailed information
az connectedmachine show \
    --name my-server \
    --resource-group rg-arc-servers

# Check agent status
az connectedmachine show \
    --name my-server \
    --resource-group rg-arc-servers \
    --query "status"

# Add tags
az connectedmachine update \
    --name my-server \
    --resource-group rg-arc-servers \
    --tags Environment=Production Role=WebServer

Querying with Resource Graph

# Find all Arc servers by OS
az graph query -q "
    Resources
    | where type == 'microsoft.hybridcompute/machines'
    | summarize count() by tostring(properties.osName)
"

# Find servers not reporting
az graph query -q "
    Resources
    | where type == 'microsoft.hybridcompute/machines'
    | where properties.status != 'Connected'
    | project name, resourceGroup, properties.status, properties.lastStatusChange
"

# Servers without specific tag
az graph query -q "
    Resources
    | where type == 'microsoft.hybridcompute/machines'
    | where tags.Environment == ''
    | project name, resourceGroup, location
"

Azure Policy for Hybrid

Apply policies across all servers:

{
  "mode": "Indexed",
  "policyRule": {
    "if": {
      "allOf": [
        {
          "field": "type",
          "equals": "Microsoft.HybridCompute/machines"
        },
        {
          "field": "Microsoft.HybridCompute/machines/osType",
          "equals": "linux"
        }
      ]
    },
    "then": {
      "effect": "deployIfNotExists",
      "details": {
        "type": "Microsoft.HybridCompute/machines/extensions",
        "existenceCondition": {
          "allOf": [
            {
              "field": "Microsoft.HybridCompute/machines/extensions/type",
              "equals": "AzureMonitorLinuxAgent"
            },
            {
              "field": "Microsoft.HybridCompute/machines/extensions/provisioningState",
              "equals": "Succeeded"
            }
          ]
        },
        "roleDefinitionIds": [
          "/providers/Microsoft.Authorization/roleDefinitions/9980e02c-c2be-4d73-94e8-173b1dc7cf3c"
        ],
        "deployment": {
          "properties": {
            "mode": "incremental",
            "template": {
              "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
              "contentVersion": "1.0.0.0",
              "parameters": {
                "vmName": {
                  "type": "string"
                },
                "location": {
                  "type": "string"
                }
              },
              "resources": [
                {
                  "type": "Microsoft.HybridCompute/machines/extensions",
                  "apiVersion": "2021-05-20",
                  "name": "[concat(parameters('vmName'), '/AzureMonitorLinuxAgent')]",
                  "location": "[parameters('location')]",
                  "properties": {
                    "publisher": "Microsoft.Azure.Monitor",
                    "type": "AzureMonitorLinuxAgent",
                    "autoUpgradeMinorVersion": true
                  }
                }
              ]
            }
          }
        }
      }
    }
  }
}

Assign the policy:

# Create policy definition
az policy definition create \
    --name "deploy-azure-monitor-agent-linux" \
    --display-name "Deploy Azure Monitor Agent on Arc-enabled Linux servers" \
    --mode Indexed \
    --rules @policy-rule.json

# Assign to resource group
az policy assignment create \
    --name "arc-monitor-agent" \
    --policy "deploy-azure-monitor-agent-linux" \
    --scope "/subscriptions/your-sub/resourceGroups/rg-arc-servers" \
    --mi-system-assigned \
    --location eastus

Extensions for Arc Servers

Deploy VM Extensions

# Install monitoring extension
az connectedmachine extension create \
    --machine-name my-server \
    --resource-group rg-arc-servers \
    --name AzureMonitorLinuxAgent \
    --type AzureMonitorLinuxAgent \
    --publisher Microsoft.Azure.Monitor \
    --auto-upgrade-minor-version true

# Install Custom Script extension
az connectedmachine extension create \
    --machine-name my-server \
    --resource-group rg-arc-servers \
    --name CustomScript \
    --type CustomScript \
    --publisher Microsoft.Azure.Extensions \
    --settings '{"commandToExecute": "apt-get update && apt-get install -y nginx"}'

# List installed extensions
az connectedmachine extension list \
    --machine-name my-server \
    --resource-group rg-arc-servers \
    --output table

Bicep Template for Extensions

param machineName string
param location string

resource arcMachine 'Microsoft.HybridCompute/machines@2021-05-20' existing = {
  name: machineName
}

resource monitoringExtension 'Microsoft.HybridCompute/machines/extensions@2021-05-20' = {
  parent: arcMachine
  name: 'AzureMonitorWindowsAgent'
  location: location
  properties: {
    publisher: 'Microsoft.Azure.Monitor'
    type: 'AzureMonitorWindowsAgent'
    autoUpgradeMinorVersion: true
  }
}

resource dependencyAgent 'Microsoft.HybridCompute/machines/extensions@2021-05-20' = {
  parent: arcMachine
  name: 'DependencyAgentWindows'
  location: location
  properties: {
    publisher: 'Microsoft.Azure.Monitoring.DependencyAgent'
    type: 'DependencyAgentWindows'
    autoUpgradeMinorVersion: true
  }
  dependsOn: [
    monitoringExtension
  ]
}

Azure Monitor Integration

Data Collection Rules

{
  "location": "eastus",
  "properties": {
    "dataSources": {
      "performanceCounters": [
        {
          "name": "perfCounterDataSource",
          "streams": ["Microsoft-Perf"],
          "samplingFrequencyInSeconds": 60,
          "counterSpecifiers": [
            "\\Processor(_Total)\\% Processor Time",
            "\\Memory\\Available Bytes",
            "\\LogicalDisk(_Total)\\% Free Space",
            "\\Network Interface(*)\\Bytes Total/sec"
          ]
        }
      ],
      "syslog": [
        {
          "name": "syslogDataSource",
          "streams": ["Microsoft-Syslog"],
          "facilityNames": ["auth", "authpriv", "daemon", "syslog"],
          "logLevels": ["Warning", "Error", "Critical"]
        }
      ]
    },
    "destinations": {
      "logAnalytics": [
        {
          "workspaceResourceId": "/subscriptions/.../workspaces/my-workspace",
          "name": "la-destination"
        }
      ]
    },
    "dataFlows": [
      {
        "streams": ["Microsoft-Perf", "Microsoft-Syslog"],
        "destinations": ["la-destination"]
      }
    ]
  }
}

KQL Queries for Arc Servers

// Arc server health overview
Heartbeat
| where ResourceType == "Microsoft.HybridCompute/machines"
| summarize LastHeartbeat = max(TimeGenerated) by Computer
| extend Status = iff(LastHeartbeat < ago(5m), "Offline", "Online")
| project Computer, LastHeartbeat, Status

// Performance across hybrid estate
Perf
| where ObjectName == "Processor" and CounterName == "% Processor Time"
| where Computer in (
    Resources
    | where type == "microsoft.hybridcompute/machines"
    | project name
)
| summarize AvgCPU = avg(CounterValue) by Computer, bin(TimeGenerated, 1h)
| render timechart

// Security events from Arc servers
SecurityEvent
| where Computer in (
    Resources
    | where type == "microsoft.hybridcompute/machines"
    | project name
)
| where EventID in (4624, 4625, 4648)  // Logon events
| summarize Count = count() by Computer, EventID
| render barchart

Microsoft Defender Integration

Enable Defender for Cloud on Arc servers:

# Enable Defender for Servers
az security pricing create \
    --name VirtualMachines \
    --tier Standard

# Check Defender status on Arc server
az connectedmachine show \
    --name my-server \
    --resource-group rg-arc-servers \
    --query "identity"

# View security recommendations
az security recommendation list \
    --query "[?contains(resourceDetails.id, 'Microsoft.HybridCompute')]"

Security assessment query:

// Vulnerabilities on Arc servers
SecurityRecommendation
| where RecommendationName contains "vulnerability"
| where ResourceId contains "Microsoft.HybridCompute"
| project Computer = tostring(split(ResourceId, "/")[-1]),
          Recommendation = RecommendationName,
          Severity = RecommendationSeverity,
          State = RecommendationState
| where State != "Healthy"

Azure Arc-enabled servers bring Azure’s management capabilities to your entire server estate, regardless of where those servers run. This unified management plane simplifies operations and improves security posture across hybrid environments.

Resources

Michael John Pena

Michael John Pena

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.