Skip to content

CHR: Cloud-Init and First-Boot Configuration

CHR: Cloud-Init and First-Boot Configuration

Section titled “CHR: Cloud-Init and First-Boot Configuration”

RouterOS CHR supports automated first-boot configuration on cloud platforms through each provider’s metadata service. When CHR starts for the first time, it reads configuration data passed via the cloud provider’s user-data or startup-script mechanism and executes it as a RouterOS script.

This allows automated provisioning without manual console access: setting passwords, assigning IP addresses, configuring firewall rules, and more.

CHR reads user-data from the cloud platform’s metadata service (typically at 169.254.169.254) during first boot. The content is treated as a RouterOS CLI script and executed once.

Key behavior:

  • Executed once on first boot only
  • Content is interpreted as RouterOS CLI commands, not standard cloud-init YAML
  • Script execution errors appear in /log print
  • No output is written back to the cloud platform — verify results by connecting to the router

CHR does not support standard cloud-init YAML format (e.g., #cloud-config with users:, packages:, runcmd: keys). Use RouterOS CLI syntax only.

User data must be plain RouterOS CLI commands, one per line:

/user set admin password=SecurePassword123!
/ip service disable telnet,ftp,www
/system identity set name=my-router

Multi-line commands using RouterOS braces are supported:

/ip firewall filter
add chain=input action=accept connection-state=established,related comment="Accept established"
add chain=input action=accept protocol=tcp dst-port=22,8291 src-address=10.0.0.0/8 comment="Management"
add chain=input action=drop comment="Drop all other input"

Pass user data in the EC2 launch wizard under Advanced Details → User data, or via the CLI:

Terminal window
aws ec2 run-instances \
--image-id ami-xxxxx \
--instance-type t3.small \
--user-data file://chr-init.rsc \
--key-name my-key \
--security-group-ids sg-xxxxx \
--subnet-id subnet-xxxxx

Use the startup-script metadata key:

Terminal window
gcloud compute instances create chr-router \
--image mikrotik-chr-7x \
--machine-type e2-small \
--can-ip-forward \
--metadata-from-file startup-script=chr-init.rsc \
--zone us-central1-a

Azure passes custom data to the VM via the --custom-data parameter:

Terminal window
az vm create \
--resource-group chr-resources \
--name chr-router \
--attach-os-disk chr-os-disk \
--os-type Linux \
--size Standard_B2s \
--nics chr-nic \
--custom-data chr-init.rsc

Most cloud providers that support user-data pass it via the standard cloud metadata endpoint at http://169.254.169.254/latest/user-data or provider-specific equivalents. CHR reads from this endpoint automatically on first boot.

:::note Interface Mapping in Cloud Deployments In cloud CHR deployments (AWS, GCP, Azure, etc.), ether1 is typically the management interface, not WAN. Cloud providers attach interfaces in the order they are defined in the VM configuration, and the first interface is usually your management or primary network interface.

The examples below use ether1 as WAN for illustration. Before deploying, check your cloud provider’s documentation or console to confirm which interface corresponds to which network. Using the wrong interface in your startup script can result in loss of management access. :::

# chr-hardening.rsc — minimum security configuration
/user set admin password=StrongPassword123!
/ip service disable telnet,ftp,www,api,api-ssl
/ip service set ssh port=22 address=10.0.0.0/8
/ip service set winbox port=8291 address=10.0.0.0/8
/tool bandwidth-server set enabled=no
/ip neighbor discovery-settings set discover-interface-list=none
/system identity set name=chr-prod-01
# chr-network.rsc — initial IP configuration
/ip address add address=10.10.1.1/24 interface=ether1 comment="WAN"
/ip address add address=192.168.100.1/24 interface=ether2 comment="LAN"
/ip route add dst-address=0.0.0.0/0 gateway=10.10.1.254 comment="Default route"
/ip dns set servers=1.1.1.1,8.8.8.8 allow-remote-requests=no
# chr-firewall.rsc — basic input protection
/ip firewall filter
add chain=input action=accept connection-state=established,related comment="Accept established/related"
add chain=input action=accept connection-state=untracked comment="Accept untracked"
add chain=input action=drop connection-state=invalid comment="Drop invalid"
add chain=input action=accept protocol=icmp comment="Accept ICMP"
add chain=input action=accept protocol=tcp dst-port=22,8291 src-address=10.0.0.0/8 comment="Management"
add chain=input action=drop comment="Drop all other input"
/ip firewall filter
add chain=forward action=accept connection-state=established,related comment="Accept established/related"
add chain=forward action=drop connection-state=invalid comment="Drop invalid"
add chain=forward action=accept out-interface=ether1 comment="Allow LAN to WAN"
# chr-nat.rsc — source NAT for LAN-to-WAN traffic
/ip firewall nat
add chain=srcnat out-interface=ether1 action=masquerade comment="Masquerade LAN traffic"
# chr-ssh-keys.rsc — add SSH public key for admin user
/user ssh-keys import public-key-file=sftp://admin@192.168.88.1/id_rsa.pub user=admin

Alternatively, pre-stage the key using RouterOS script syntax:

/file add name=admin.pub contents="ssh-rsa AAAAB3NzaC1yc2EAAAADAQAB... user@host"
/user ssh-keys import public-key-file=admin.pub user=admin
# chr-license.rsc — activate CHR license (run after first boot)
/system license generate-new-id
/system/license renew account=your-mikrotik-account password=your-password level=p1

Run /system license generate-new-id before requesting a license if your instance was cloned or launched from a shared AMI/image. Multiple instances from the same image may share a system ID.

chr-complete-init.rsc
# Complete first-boot initialization for cloud CHR deployment
# 1. Identity
/system identity set name=cloud-chr-01
# 2. Security — change default password immediately
/user set admin password=SecurePassword123!
# 3. Disable unused management services
/ip service disable telnet,ftp,www,api,api-ssl
/ip service set ssh address=10.0.0.0/8
/ip service set winbox address=10.0.0.0/8
# 4. Disable bandwidth server and neighbor discovery
/tool bandwidth-server set enabled=no
/ip neighbor discovery-settings set discover-interface-list=none
# 5. Network configuration
/ip address add address=10.10.1.1/24 interface=ether1 comment="WAN"
/ip address add address=192.168.100.1/24 interface=ether2 comment="LAN"
/ip route add dst-address=0.0.0.0/0 gateway=10.10.1.254
# 6. DNS
/ip dns set servers=1.1.1.1,8.8.8.8
# 7. Firewall — input chain
/ip firewall filter
add chain=input action=accept connection-state=established,related
add chain=input action=drop connection-state=invalid
add chain=input action=accept protocol=icmp
add chain=input action=accept protocol=tcp dst-port=22,8291 src-address=10.0.0.0/8
add chain=input action=drop

Warning — management lockout risk: The rule above only permits SSH and Winbox access from 10.0.0.0/8. If your management workstation has a public IP address (common in cloud environments), the drop all rule will block your access immediately after the script runs. Before executing this script, replace src-address=10.0.0.0/8 with your actual management IP or subnet — for example, src-address=203.0.113.10/32. If you need to permit access on a specific interface regardless of source IP, add in-interface=ether1 to the accept rule instead of (or in addition to) the src-address restriction.

# 8. Firewall — forward chain
/ip firewall filter
add chain=forward action=accept connection-state=established,related
add chain=forward action=drop connection-state=invalid
# 9. NAT
/ip firewall nat add chain=srcnat out-interface=ether1 action=masquerade
# 10. Generate new system ID (important when launching from a shared image)
/system license generate-new-id
# 11. Log completion
/log info "CHR first-boot initialization complete"

After boot, verify the script ran correctly:

# Check system log for script output and errors
/log print where topics~"script"
# Verify identity was set
/system identity print
# Verify interfaces and IPs
/ip address print
# Check firewall rules applied
/ip firewall filter print
  • Verify the script was passed correctly (check cloud provider console for user-data)
  • Check /log print for any error messages
  • User data executes only on first boot — rebooting will not re-execute it
  • Ensure the script uses RouterOS CLI syntax, not bash or YAML

If the first-boot script included a drop all input rule and you can no longer reach the router, the management source address did not match the permitted subnet. To recover:

  1. Use the cloud provider’s serial console or out-of-band access (AWS EC2 Instance Connect, GCP Serial Port, Azure Serial Console) — these bypass firewall rules.
  2. Once connected, remove or relax the offending rule:
# Identify the rule number
/ip firewall filter print
# Remove the drop rule (adjust number to match your output)
/ip firewall filter remove [find chain=input action=drop]
# Re-add with your actual management IP
/ip firewall filter add chain=input action=accept protocol=tcp dst-port=22,8291 src-address=<YOUR-IP>/32
/ip firewall filter add chain=input action=drop
  1. To prevent recurrence, update the src-address in your cloud-init script before the next deployment.

RouterOS scripts execute line-by-line. If a command fails, execution continues with the next line. Check the log for specific errors:

/log print where message~"error"

If the password script ran but the password did not change, verify there are no quoting issues in the user-data string. Test the command manually in the CLI:

/user set admin password=TestPassword123