Azure Korea Account How to Run Startup Scripts on Azure VM

Azure Account / 2026-05-20 13:53:27

Why Startup Scripts Matter (And Why They Sometimes Don’t)

So you’ve got an Azure VM. It boots up. It looks at you politely. Then nothing else happens, because your server didn’t magically read your mind and decide to install packages, configure services, or deploy that app you swore you’d “just set up later.” That’s where startup scripts come in: the automated little gremlins that run when the VM starts, doing the boring setup work on your behalf.

In Azure, “startup scripts” can mean a few different things. You can use VM extensions (which are Azure-managed plugins that run code on the VM), or you can bake logic into the OS (like cloud-init on Linux). You can also run scripts via configuration management tools (like Ansible, Chef, or Puppet), but this article focuses on straightforward, reliable ways to run startup scripts specifically in the Azure VM context.

We’ll walk through a clear set of options, explain what’s actually happening under the hood, show examples for Linux and Windows, and then tackle the usual “why didn’t it run?” mystery tour. By the end, you’ll have a setup that’s both dependable and not held together with duct tape and hope.

Decide What “Startup” Means for Your VM

Before you pick a method, decide what you expect at boot time. Startup scripts are commonly used to:

  • Install software (agents, dependencies, runtimes)
  • Configure system settings (users, firewall rules, kernel parameters)
  • Download and deploy applications
  • Register the VM with a service (monitoring, CI runners, etc.)

However, boot timing matters. A VM boot is not the same as “all networks are ready,” “all disks are attached,” or “DNS is behaving.” If your script immediately calls external APIs and the network isn’t fully up, you’ll get intermittent failures—those are the most annoying failures because they don’t always reproduce.

A good startup script should be:

  • Idempotent: If it runs again, it shouldn’t break things.
  • Observable: You should be able to find logs.
  • Defensive: It should handle transient errors (retries, timeouts).
  • Secure: Avoid hardcoding secrets.

Three Common Approaches in Azure

Let’s look at the main approaches you’ll see for startup-like automation on Azure VMs:

1) Azure VM Custom Script Extension

This is the most common “run my script on the VM” method. Azure provides a VM extension that fetches and runs your script. It’s straightforward and great for many setup tasks.

Pros:

  • Easy to configure via portal or CLI
  • Supports Linux and Windows
  • Provides extension status and logs

Cons:

  • It’s not exactly “run at every boot automatically” unless you configure it that way (depending on how you deploy it)
  • You must design for re-runs if you trigger it again

2) Azure VM Extension for Continuous Configuration

Some extensions (or workflows using them) are used for continuous configuration. This typically involves either re-running scripts when the VM is created, or setting up logic that runs at boot using OS-level mechanisms (like systemd timers or startup tasks).

Pros:

  • More flexible when you want repeated behavior
  • Works nicely with OS-native scheduling

Cons:

  • More moving parts
  • You still have to handle OS-level boot ordering

3) OS-Native Startup: cloud-init (Linux) and Startup Tasks (Windows)

On Linux distributions, cloud-init is a common solution for first-boot configuration (and sometimes subsequent boots, depending on config). On Windows, you can use Scheduled Tasks or VM startup scripts depending on your environment.

Pros:

  • Very native and can integrate with boot timing
  • Azure Korea Account Often easiest for “first boot” provisioning

Cons:

  • Requires OS-level configuration and familiarity
  • Not as uniform across all images and setups

In this article, we’ll focus on VM Custom Script Extension for practical startup automation, then show how to make it run reliably at each boot by delegating boot behavior to the OS itself.

Prerequisites: Network, Permissions, and Script Hygiene

Before you unleash your startup gremlins, do these housekeeping chores:

Check your outbound connectivity

Your script might install packages from the internet, download artifacts, or call APIs. Make sure the VM has:

  • Outbound internet access (or proper proxy/NAT)
  • Correct DNS resolution
  • Firewall rules that won’t block required traffic

If you don’t know, don’t guess. Put a quick connectivity test at the beginning of your script. Yes, it’s extra lines. Yes, it saves your sanity.

Use a dedicated user or proper permissions

On Linux, installing packages and writing system files usually needs root privileges. On Windows, you typically need Administrator privileges.

Also, be mindful: some scripts fail because they’re run under the wrong user. Extension frameworks typically run with system-level privileges, but if you change defaults, you might accidentally drop privileges.

Make your script robust and logged

Without logging, you’re basically asking the VM to perform surgery while blindfolded and then telling yourself you’ll “figure it out later.” Don’t do that.

Write logs to a dedicated directory, include timestamps, and capture output and errors. If something fails, your future self will thank you.

Option A: Use Azure VM Custom Script Extension (Linux)

Let’s start with Linux, because it’s the land of scripts that run with the confidence of a raccoon in a trash can.

Step 1: Prepare your script

Create a shell script that does the work. Example: install Nginx and drop a simple “hello” page.

#!/usr/bin/env bash
set -euo pipefail

LOG_DIR="/var/log/startup-script"
LOG_FILE="$LOG_DIR/run.log"

mkdir -p "$LOG_DIR"
exec >>"$LOG_FILE" 2>&1

echo "[$(date -Is)] Startup script started."

# Idempotency example: check if nginx is already installed
if command -v nginx &>/dev/null; then
  echo "[$(date -Is)] nginx already present. Skipping install."
else
  echo "[$(date -Is)] Installing nginx..."
  export DEBIAN_FRONTEND=noninteractive
  apt-get update -y
  apt-get install -y nginx
fi

echo "[$(date -Is)] Configuring default page..."
cat > /var/www/html/index.html <<'EOF'
<html><body><h2>Hello from Azure VM startup script</h2></body></html>
EOF

echo "[$(date -Is)] Enabling and starting nginx..."
systemctl enable nginx
systemctl restart nginx

echo "[$(date -Is)] Startup script completed successfully."

Notes:

  • set -euo pipefail helps fail fast and reduces “silent failures.”
  • mkdir -p ensures the log directory exists.
  • Idempotency prevents repeated installs from turning into repeated heartbreak.
  • Output is redirected to a log file.

Step 2: Upload the script to a location the VM can access

The extension usually downloads the script from somewhere accessible. Common methods include:

  • Store the script in a blob container
  • Provide a script file in the extension settings (less common for larger scripts)

For production setups, storing scripts in Azure Storage is typical. You can grant the VM access using managed identity or a SAS token (more on secrets later).

Step 3: Configure the extension via Azure Portal

High-level portal flow:

  • Go to your Virtual Machine
  • Under Settings or Extensions, choose to add an extension
  • Select “Custom Script” extension
  • Provide script location and command to run
  • Save, then monitor extension status

The exact UI labels vary a bit over time, because Microsoft loves to keep things exciting. If you get lost, search within the VM blade for “Extensions” or “Add extension.”

Step 4: Configure the extension via Azure CLI (example)

Here’s a conceptual example of deploying a custom script extension. Exact command parameters depend on the VM and your storage setup.

az vm extension set \
  --resource-group <rg> \
  --vm-name <vm> \
  --name CustomScript \
  --publisher Microsoft.Azure.Extensions \
  --settings '{
    "fileUris": ["<blob-url-to-script>"],
    "commandToExecute": "bash startup.sh"
  }'

Again, you’ll tailor the file URI and command. If you use private storage, you’ll need authentication (SAS token or identity).

Step 5: Verify extension execution

After you apply the extension, check:

  • Extension status in Azure portal (Succeeded/Failed)
  • Extension logs (often available in the extension blade)
  • Your script’s own log file in /var/log/startup-script/run.log

If the extension says “Succeeded” but your app isn’t deployed, that usually means one of these:

  • Your script didn’t do what you thought (wrong paths, missing permissions)
  • Your script exited early due to a condition
  • You configured a different machine than the one you’re checking

Option B: Make Linux scripts run on every boot (systemd)

Now for the part people usually want: not just “run the script once when I configure the extension,” but “run it every time the VM starts.”

The reliable way is to use the custom script extension to install a systemd service or timer. The extension runs once to set up the boot-time runner, and systemd handles the repeated execution.

Step 1: Create a systemd service file

Example service file: /etc/systemd/system/startup-runner.service

[Unit]
Description=Run startup script at boot
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/run-startup.sh
RemainAfterExit=true

[Install]
WantedBy=multi-user.target

Key details:

  • After=network-online.target helps avoid “network isn’t ready yet” errors.
  • Azure Korea Account oneshot is appropriate if your script runs and exits.

Step 2: Create the runner script

Azure Korea Account Put your actual script somewhere like /usr/local/bin/run-startup.sh.

Make it executable:

chmod +x /usr/local/bin/run-startup.sh

Your run-startup.sh should include logging just like before.

Step 3: Enable the service

From your extension-run setup script, run:

systemctl daemon-reload
systemctl enable startup-runner.service
systemctl start startup-runner.service

This sets it up for every boot and optionally runs it immediately.

Step 4: Verify systemd logs

To check service output, use:

systemctl status startup-runner.service
journalctl -u startup-runner.service --no-pager -n 200

Now you’ve got two layers of evidence: your script log and systemd/journal logs. When something goes wrong, you’ll have receipts.

Azure Korea Account Option C: VM Custom Script Extension (Windows)

Windows startup automation tends to be equally effective, just with more backslashes and a slightly stronger relationship with “Run as Administrator.” Let’s keep it simple.

Step 1: Prepare a PowerShell script

Example: install IIS and drop a simple page.

$ErrorActionPreference = 'Stop'

$logDir = 'C:\StartupScriptLogs'
if (!(Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir | Out-Null }

$logFile = Join-Path $logDir 'run.log'
Start-Transcript -Path $logFile -Append | Out-Null

Write-Host "[$(Get-Date -Format s)] Startup script started."

# Install IIS (idempotent-ish: check if feature exists)
$feature = Get-WindowsFeature -Name Web-Server
if (-not $feature.Installed) {
  Write-Host "[$(Get-Date -Format s)] Installing IIS..."
  Install-WindowsFeature -Name Web-Server -IncludeManagementTools | Out-Null
} else {
  Write-Host "[$(Get-Date -Format s)] IIS already installed."
}

# Configure default content
Write-Host "[$(Get-Date -Format s)] Writing index.html..."
$htmlPath = 'C:\inetpub\wwwroot\index.html'
@'
<html><body><h2>Hello from Azure VM startup script</h2></body></html>
'@ | Out-File -FilePath $htmlPath -Encoding utf8

Write-Host "[$(Get-Date -Format s)] Ensuring website is started..."
Start-Service W3SVC

Write-Host "[$(Get-Date -Format s)] Startup script completed successfully."
Stop-Transcript | Out-Null

Step 2: Place script where the extension can download it

Use Azure Storage blob or another reachable URL. If you use private storage, authenticate properly. Don’t put secrets in plain text in the command line unless you enjoy security theater.

Step 3: Configure the extension

Similar steps as Linux, but command looks like:

powershell -ExecutionPolicy Bypass -File startup.ps1

Azure Korea Account Depending on the extension configuration, you might need to adjust how file paths are handled.

Step 4: Verify logs

Your transcript log (C:\StartupScriptLogs\run.log) will be your first stop. If the script fails early, the transcript might still contain the last successful step.

Windows: Run your script on every boot (Scheduled Task)

If you want repeated execution, use the custom script extension to install a Scheduled Task that runs at startup.

Step 1: Create a Scheduled Task using PowerShell

Example task creation:

$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-ExecutionPolicy Bypass -File C:\StartupScripts\run.ps1'

$trigger = New-ScheduledTaskTrigger -AtStartup

$principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest

Register-ScheduledTask -TaskName 'AzureStartupRunner' -Action $action -Trigger $trigger -Principal $principal -Force

Put run.ps1 in a stable location like C:\StartupScripts and ensure your script is idempotent.

Step 2: Verify the task

Get-ScheduledTask -TaskName 'AzureStartupRunner'

You can also check Task Scheduler UI if you enjoy clicking.

Step 3: Check history and event logs

Scheduled tasks write event logs you can inspect. Your script’s own logging should help too.

How to Handle Secrets Without Summoning a Security Incident

Startup scripts often need secrets: API keys, database passwords, tokens, enrollment keys, and so on. Here’s the rule: don’t hardcode secrets into scripts embedded in extension settings. Ever.

Instead, use one of these patterns:

  • Azure Korea Account Managed identity + Key Vault (best for Azure-native apps)
  • Store secrets in Key Vault and fetch at runtime
  • For storage SAS tokens, use short-lived tokens and limit permissions
  • Use environment variables provided securely, if your workflow supports it

Then your startup script can retrieve the secret when it runs.

Idempotency: The “Please Don’t Break Me When I Re-run” Principle

If you run startup scripts once and then never again, you can ignore idempotency. But reality is rude. Extensions might rerun. You might redeploy. A VM could be rebuilt, or you might use the same script across multiple machines that have different states.

Idempotency means your script can safely run multiple times without causing duplicate installs, conflicting config, or broken services. Common techniques:

  • Check if a package exists before installing
  • Check if a config line already exists before appending
  • Use deterministic file paths
  • Restart services only when changes occur

For example, on Linux, instead of blindly installing Nginx every time, check if it’s already present. On Windows, check if the IIS feature is installed before installing it again.

Common Failure Modes (And How to Survive Them)

If your startup script fails, it’s usually one of the following. Consider this your “startup script survival guide,” minus the heroic music.

1) Line endings: The classic “works on my laptop” trap

Azure Korea Account If you edit a shell script on Windows, it might end up with CRLF line endings. Bash sometimes chokes on that. Fix by converting to Unix line endings.

In practice:

  • Ensure scripts are saved with LF
  • Or run a conversion step before executing

2) Script permissions: “Permission denied” is not a personality trait

On Linux, make sure the script is executable:

chmod +x startup.sh

If you download the script via extension, you may need to set permissions after download. Decide where that happens in your workflow.

Azure Korea Account 3) Wrong working directory

Scripts that rely on relative paths can fail. Use absolute paths for important files, or set the working directory explicitly.

4) Network not ready

This is the “it fails sometimes” bug. On Linux, systemd network-online target helps. On Windows, use retries with backoff and timeouts before contacting external services.

A good pattern is: attempt the network operation multiple times with short waits, rather than giving up immediately.

5) Apt/yum lock contention

Package managers can be busy during boot (or due to other agents). If you see lock-related errors, retry after a delay.

In other words: don’t fight the package manager. Negotiate.

6) Output and logs not captured

If you don’t redirect stdout/stderr to a log file (or use transcripts), you lose the evidence trail. Always capture logs.

7) Using secrets in extension settings

Even if it “works,” it’s risky. Use Key Vault/managed identity. Your security team might not be thrilled, and your future self won’t be either.

Troubleshooting Checklist: Confirm It Ran

When your startup script “doesn’t seem to run,” you need to confirm at least three things:

  • The extension/task was deployed successfully
  • The script process executed
  • The expected system changes happened

Check Azure extension status

Azure Korea Account In Azure portal, locate the VM’s extension and confirm status. If it failed, read the extension logs. Many failures will include error messages like missing files, command errors, or authentication problems.

Check VM-side logs

Linux:

  • /var/log/startup-script/run.log (or your chosen location)
  • systemd journal for your service

Windows:

  • Transcript logs in your chosen directory
  • Windows Event Viewer logs for scheduled tasks and failures

Check the state you expected

For example:

  • Nginx service is running (Linux: systemctl status nginx)
  • Web-Server feature is installed (Windows: Get-WindowsFeature)
  • Your application files exist in the expected directory

Best Practices for a “Production-Ready” Startup Script

If you want your startup script to feel like a reliable employee and not a chaotic intern, follow these best practices:

  • Keep scripts small: Delegate big workflows to a proper deploy tool if needed.
  • Use retries for network calls: Exponential backoff is your friend.
  • Pin versions: Install specific versions of runtimes where possible.
  • Use deterministic configuration: Avoid “random” settings that change behavior across runs.
  • Write clear logs: Include step names and timestamps.
  • Fail loudly: If a critical step fails, exit with a non-zero code so you can detect it.

Putting It All Together: A Practical Workflow

Here’s a workflow you can adapt:

  • Create a script (Linux bash or Windows PowerShell) with logging and idempotency.
  • Store the script in Azure Storage.
  • Deploy a Custom Script Extension to run an installation step once.
  • That installation step registers a boot-time mechanism (systemd on Linux, Scheduled Task on Windows).
  • On every boot, the OS runner executes your script safely.
  • Use logs to confirm each run and troubleshoot if needed.

This approach gives you:

  • Clear control over “setup once” vs. “run on every boot”
  • Better handling of network timing
  • More reliable evidence when something goes wrong

FAQ: People Ask This Stuff

Does the Azure Custom Script Extension run every time the VM restarts?

Usually, the extension is applied when you deploy it (for example, during VM creation or when you run the extension command). If you want it on every boot, the best pattern is to have the extension set up an OS-native runner like systemd or a Scheduled Task.

Can I run multiple scripts?

Yes. You can either:

  • Run multiple commands in one startup script, or
  • Deploy multiple extensions, or
  • Use one “bootstrap” script that orchestrates others

One bootstrap script is often the easiest to reason about.

How do I handle script updates later?

Update the script in storage and update the extension or scheduled task runner accordingly. If your OS runner points to a fixed path, you can have it download the latest version each boot (again, with retries and logging).

What about race conditions with attached disks?

If your script expects additional disks, ensure they’re attached and mounted before you use them. On Linux, check disk presence and mount status. On systemd services, ordering targets can help, but you may still need to verify devices exist and mount points are ready.

Final Thoughts: Make the VM Tell You What Happened

Startup scripts are like helpful alarms. They can do a lot of work, but only if you design them well and listen to them when they go off. If your script is idempotent, logs everything, and handles network timing and permissions, you’ll spend far less time playing “guess the failure.”

And if you ever get stuck: check extension status first, then check your script logs, then check your OS service/task logs. That order alone will save you hours of confusion.

Now go forth and teach your Azure VMs to behave like responsible adults at boot time. The only thing they should do unexpectedly is impress you.

TelegramContact Us
CS ID
@cloudcup
TelegramSupport
CS ID
@yanhuacloud