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.

# 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