Skip to content
Menu

Networking10 min read

Network Configuration

Configuring networking via cloud-init and Netplan

Network Configuration via Cloud-Init

Cloud-init supports two network configuration formats. The format is specified in the network-config data provided by the datasource or by the user (e.g., NoCloud's network-config file).

  1. Datasource

    Provides network-config (or user overrides it)

  2. cloud-init-local

    Reads network-config in init-local stage

  3. Renderer

    Converts to Netplan, ENI, or networkd format

  4. Networking Up

    systemd-networkd or NetworkManager applies config

Note

Key distinction: Cloud-init's network-config format (v1/v2) is separate from Netplan YAML, even though v2 looks nearly identical. Cloud-init's v2 network-config is rendered into Netplan YAML (on Ubuntu) or directly into systemd-networkd/ENI files (on other distros). The renderer depends on what the distro uses.

V1 vs V2 Network Config

V1 (ENI-Style, Legacy)

Modeled after /etc/network/interfaces. Still supported but deprecated in favor of v2.

yaml
network:
  version: 1
  config:
    - type: physical
      name: eth0
      mac_address: "aa:bb:cc:dd:ee:f0"
      subnets:
        - type: static
          address: 10.0.0.10/24
          gateway: 10.0.0.1
          dns_nameservers:
            - 8.8.8.8
            - 1.1.1.1
    - type: nameserver
      address:
        - 8.8.8.8
      search:
        - internal.corp
  • Flat list of config entries
  • Each entry has a type (physical, bond, vlan, bridge, nameserver)
  • Subnets are nested under interfaces
  • Verbose and less intuitive
V2 (Netplan YAML, Modern)

Follows Netplan's schema. The recommended format for all new configurations.

yaml
network:
  version: 2
  ethernets:
    eth0:
      match:
        macaddress: "aa:bb:cc:dd:ee:f0"
      addresses:
        - 10.0.0.10/24
      routes:
        - to: default
          via: 10.0.0.1
      nameservers:
        addresses: [8.8.8.8, 1.1.1.1]
        search: [internal.corp]
  • Structured by interface type (ethernets, bonds, vlans, bridges)
  • Cleaner, more readable
  • Direct mapping to Netplan YAML
  • Supports all modern features (routes, routing-policy, etc.)

Tip

Always use v2. V1 exists for backward compatibility with old OpenStack deployments and legacy images. Any new configuration should use v2 — it's more expressive, better documented, and directly maps to Netplan.

Netplan V2 Configuration Examples

Static IP

yaml
network:
  version: 2
  ethernets:
    eth0:
      addresses:
        - 10.0.0.10/24
      routes:
        - to: default
          via: 10.0.0.1
      nameservers:
        addresses: [8.8.8.8, 1.1.1.1]
        search: [internal.corp, prod.internal.corp]

Note

Note: gateway4 is deprecated in Netplan 0.104+. Use a routes entry with to: default instead. Cloud-init v2 follows the same deprecation — always use the routes syntax.

DHCP

bash
network:
  version: 2
  ethernets:
    eth0:
      dhcp4: true
      dhcp6: false
      dhcp4-overrides:
        use-dns: false          # ignore DHCP-provided DNS, use our own
        use-routes: true
      nameservers:
        addresses: [10.0.0.2]  # internal DNS resolver

Bonds

Bonding two interfaces for redundancy or throughput. Common in bare-metal and dedicated host scenarios.

yaml
network:
  version: 2
  ethernets:
    eth0:
      dhcp4: false
    eth1:
      dhcp4: false
  bonds:
    bond0:
      interfaces: [eth0, eth1]
      addresses:
        - 10.0.0.10/24
      routes:
        - to: default
          via: 10.0.0.1
      parameters:
        mode: 802.3ad           # LACP
        lacp-rate: fast
        mii-monitor-interval: 100
        transmit-hash-policy: layer3+4
      nameservers:
        addresses: [8.8.8.8]

Common Bond Modes

Mode Name Use Case
active-backup Mode 1 Simple failover — one active, one standby. Works without switch support.
802.3ad Mode 4 (LACP) Link aggregation. Requires switch LACP support. Best for throughput + redundancy.
balance-alb Mode 6 Adaptive load balancing. No switch support needed. Good for mixed environments.

VLANs

yaml
network:
  version: 2
  ethernets:
    eth0:
      dhcp4: false
  vlans:
    eth0.100:
      id: 100
      link: eth0
      addresses:
        - 10.100.0.10/24
      routes:
        - to: 10.100.0.0/16
          via: 10.100.0.1
    eth0.200:
      id: 200
      link: eth0
      addresses:
        - 10.200.0.10/24
      nameservers:
        addresses: [10.200.0.2]

Bridges

Bridge interfaces are essential for hosting VMs (libvirt/QEMU) or containers that need L2 network access.

yaml
network:
  version: 2
  ethernets:
    eth0:
      dhcp4: false
  bridges:
    br0:
      interfaces: [eth0]
      addresses:
        - 10.0.0.10/24
      routes:
        - to: default
          via: 10.0.0.1
      nameservers:
        addresses: [8.8.8.8, 1.1.1.1]
      parameters:
        stp: false              # disable STP for single-host bridges
        forward-delay: 0       # no forwarding delay
      mtu: 9000

Tip

Proxmox/libvirt note: When provisioning a hypervisor via cloud-init, bridged networking is the standard pattern. The physical NIC (eth0) becomes a bridge member with no IP, and the bridge (br0) gets the IP. Guest VMs then attach to br0 for direct L2 access to the network.

Static Routes

yaml
network:
  version: 2
  ethernets:
    eth0:
      addresses:
        - 10.0.0.10/24
      routes:
        - to: default
          via: 10.0.0.1
        - to: 10.10.0.0/16           # route to internal network
          via: 10.0.0.254
          metric: 100
        - to: 172.16.0.0/12          # route to VPN network
          via: 10.0.0.253
          metric: 200
    eth1:                              # management interface, separate route table
      addresses:
        - 192.168.1.10/24
      routes:
        - to: 192.168.0.0/16
          via: 192.168.1.1
          table: 100             # policy routing table
      routing-policy:
        - from: 192.168.1.10
          table: 100

DNS Configuration

yaml
network:
  version: 2
  ethernets:
    eth0:
      dhcp4: true
      nameservers:
        addresses:
          - 10.0.0.2              # primary internal DNS
          - 10.0.0.3              # secondary internal DNS
          - 8.8.8.8               # fallback public DNS
        search:
          - prod.internal.corp    # short names resolve here first
          - internal.corp
          - corp

MTU Configuration

yaml
network:
  version: 2
  ethernets:
    eth0:                              # standard internet-facing interface
      dhcp4: true
      mtu: 1500
    eth1:                              # internal network with jumbo frames
      addresses:
        - 10.0.0.10/24
      mtu: 9001                     # AWS max MTU for placement group / ENA

Note

AWS MTU: EC2 instances within the same VPC support up to 9001 bytes MTU (jumbo frames) when using ENA. Cross-VPC and internet traffic is limited to 1500. Setting MTU 9001 on internal interfaces reduces CPU overhead for high-throughput workloads (fewer packets per byte transferred). Always verify with ping -M do -s 8972 <target> (8972 + 28 bytes IP/ICMP header = 9000).

Netplan: Ubuntu's Network Configuration Layer

Netplan is not a network daemon — it's a YAML abstraction layer that sits between you and the actual networking backend. Understanding this architecture is key.

  1. Netplan YAML

    /etc/netplan/*.yaml

  2. netplan generate

    Renders YAML to backend config

  3. Backend

    systemd-networkd OR NetworkManager

  4. Network Up

    Interfaces configured, routes applied

systemd-networkd (Default on Servers)
  • Lightweight, headless, no GUI dependencies
  • Default on Ubuntu Server, cloud images
  • Config rendered to /run/systemd/network/
  • Managed via networkctl
  • Ideal for servers, containers, and VMs
bash
network:
  version: 2
  renderer: networkd    # explicit (default on server)
  ethernets:
    eth0:
      dhcp4: true
NetworkManager (Default on Desktops)
  • Feature-rich, GUI integration (GNOME, KDE)
  • Default on Ubuntu Desktop
  • Config rendered to /etc/NetworkManager/system-connections/
  • Managed via nmcli / nmtui
  • Better for WiFi, VPN, user-switching scenarios
bash
network:
  version: 2
  renderer: NetworkManager
  ethernets:
    eth0:
      dhcp4: true

Key Netplan Commands

Command Description
netplan generate Render YAML to backend config files (dry-run — does not apply)
netplan apply Generate + apply immediately. Danger: if config is wrong, you lose connectivity.
netplan try Apply temporarily, auto-revert after 120 seconds if not confirmed. Safe for remote changes.
netplan get Show the merged/active configuration (Netplan 0.106+)
netplan status Show interface status with Netplan context (Netplan 0.106+)

Warning

Never use netplan apply over SSH without a fallback. If your config has an error that kills networking, you're locked out. Always use netplan try — it applies the config and starts a 120-second timer. If you don't press Enter to confirm, it rolls back to the previous config automatically. This is your safety net for remote network changes.

Cloud-Init Network Config Sources

Where does cloud-init get its network configuration? The source depends on the datasource and can be overridden by the user.

  1. Datasource default — The cloud provider's metadata service provides a default network config. On AWS, this is DHCP. On OpenStack, it might be static IPs from Neutron. Cloud-init applies this by default if no override exists.
  2. User override (NoCloud) — For local VMs, you provide a network-config file alongside user-data and meta-data on the seed ISO or directory. This completely replaces the datasource default.
  3. Drop-in files/etc/cloud/cloud.cfg.d/ can contain network: configuration that overrides the datasource. Useful for baked images where you want specific network settings regardless of the cloud.
  4. Disabling cloud-init networking — Create /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with network: {config: disabled}. Cloud-init will not touch networking at all, and you manage Netplan directly.

NoCloud Seed Directory Example

text
bash

      /var/lib/cloud/seed/nocloud/

        meta-data    # instance-id, local-hostname

        user-data    # #cloud-config YAML or script

        network-config # v2 network YAML
    

Or as a seed ISO (for libvirt/Proxmox):

console
console

      $ genisoimage -output seed.iso -volid cidata -joliet -rock \

        meta-data user-data network-config

      

      # Or with cloud-localds (from cloud-image-utils package):

      $ cloud-localds --network-config=network-config seed.iso user-data meta-data
    

Tip

Proxmox integration: Proxmox VE has native cloud-init support. When you add a Cloud-Init Drive to a VM template, Proxmox generates the NoCloud seed ISO automatically from the VM's cloud-init settings (IP config, user, SSH keys, DNS) in the Proxmox UI/API. No manual ISO creation needed.

Debugging Cloud-Init & Networking

When cloud-init networking doesn't work as expected, here's your debugging toolkit. These commands should be muscle memory.

cloud-init query: Inspect Resolved Configuration

console
console

    # Show all resolved instance data

    $ cloud-init query --all

    {

      "v1": {

        "cloud_name": "aws",

        "instance_id": "i-0abc123def456",

        "region": "us-east-1",

        ...

      },

      "ds": { ... }

    }

    

    # Query specific fields

    $ cloud-init query v1.instance_id

    i-0abc123def456

    

    $ cloud-init query ds.meta_data.network.interfaces

    {...}
  

cloud-init schema: Validate Configuration

console
console

    # Validate the running system's cloud-config

    $ cloud-init schema --system

    Valid schema /var/lib/cloud/instance/cloud-config.txt

    

    # Validate a local file before deploying

    $ cloud-init schema --config-file my-cloud-config.yaml

    Valid cloud-config: my-cloud-config.yaml

    

    # Check network-config schema

    $ cloud-init schema --config-file network-config.yaml --schema-type network-config

    Valid network-config: network-config.yaml
  

cloud-init analyze: Boot Performance

console
console

    # Show how long each stage took

    $ cloud-init analyze show

    -- Boot Record 01 --

        00.00000s (timestamp) Starting stage: init-local

        00.51200s (timestamp) Finished stage: (init-local) 00.51200 seconds

    

        01.23400s (timestamp) Starting stage: init-network

        03.45600s (timestamp) Finished stage: (init-network) 02.22200 seconds

    

        03.50000s (timestamp) Starting stage: modules-config

        15.78900s (timestamp) Finished stage: (modules-config) 12.28900 seconds

    

        15.80000s (timestamp) Starting stage: modules-final

        25.12300s (timestamp) Finished stage: (modules-final) 09.32300 seconds

    

    Total Time: 24.35400 seconds

    

    # Blame: which modules took the longest

    $ cloud-init analyze blame

    -- Boot Record 01 --

      09.12300s (modules-final/config-scripts-user)

      08.45600s (modules-config/config-apt-configure)

      03.21000s (modules-config/config-package-update-upgrade-install)

      01.02300s (init-network/consume-user-data)

      00.51200s (init-local/search-datasource)
  

Note

Performance insight: The blame output is invaluable for optimizing boot time. If apt-configure takes 8+ seconds, it's doing apt-get update on every boot (check package_update). If scripts-user takes 9+ seconds, your runcmd is slow — consider moving heavy work to a background systemd service instead.

cloud-init status: Check Current State

console
console

    $ cloud-init status --long

    status: done

    extended_status: done

    boot_status_code: enabled-by-generator

    last_update: Tue, 25 Mar 2026 14:23:45 +0000

    detail:

    DataSourceEc2Local

    

    # Wait for cloud-init to finish (useful in scripts)

    $ cloud-init status --wait

    ..............done

    status: done
  

Log Files

/var/log/cloud-init.log

The main log. Contains detailed output from every cloud-init module, including:

  • Datasource detection and probing
  • Configuration merging and rendering
  • Module execution (start/stop/errors)
  • Network config rendering
  • Schema validation results

This is where you find why something failed.

/var/log/cloud-init-output.log

Captures stdout/stderr from user scripts and runcmd commands. Contains:

  • Output from shell scripts in user-data
  • Output from runcmd commands
  • Package installation output
  • Any echo or printf from your scripts

This is where you find what your code printed.

console
console

    # Common debugging workflow

    

    # 1. Check overall status

    $ cloud-init status --long

    

    # 2. If errors, check the main log

    $ grep -i "error\|warning\|traceback" /var/log/cloud-init.log | tail -20

    

    # 3. Check script output

    $ tail -50 /var/log/cloud-init-output.log

    

    # 4. See what user-data was received

    $ cat /var/lib/cloud/instance/user-data.txt

    

    # 5. See the merged cloud-config

    $ cat /var/lib/cloud/instance/cloud-config.txt

    

    # 6. See rendered network config (Ubuntu/Netplan)

    $ cat /etc/netplan/50-cloud-init.yaml

    

    # 7. See what datasource was detected

    $ cat /run/cloud-init/ds-identify.log | tail -5

    

    # 8. Collect everything for support

    $ cloud-init collect-logs

    Wrote /tmp/cloud-init.tar.gz
  

Networking-Specific Debugging

console
console

      # Check what Netplan config cloud-init generated

      $ cat /etc/netplan/50-cloud-init.yaml

      

      # Verify Netplan can parse it

      $ netplan generate 2>&1

      

      # Check if cloud-init is managing networking

      $ grep "network:" /etc/cloud/cloud.cfg.d/*.cfg

      

      # Check rendered systemd-networkd config

      $ ls /run/systemd/network/

      

      # Check active network state

      $ networkctl status eth0

      

      # Check DNS resolution config

      $ resolvectl status

      

      # If cloud-init networking is fighting you, disable it:

      $ cat > /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg <<EOF

      network: {config: disabled}

      EOF
    

Warning

Common gotcha: Cloud-init writes /etc/netplan/50-cloud-init.yaml on every boot (by default). If you manually edit a Netplan file, cloud-init will overwrite your changes on next boot. To prevent this: either disable cloud-init networking (as shown above) or put your config in a higher-priority file like /etc/netplan/99-custom.yaml (Netplan merges files in lexicographic order, higher wins).

Quick Reference: Debugging Commands

Command Purpose
cloud-init status --long Check if cloud-init finished, and if there were errors
cloud-init status --wait Block until cloud-init finishes (useful in scripts)
cloud-init query --all Dump all resolved instance data (metadata, user-data, etc.)
cloud-init schema --system Validate the running instance's cloud-config against schema
cloud-init analyze show Show timing for each boot stage
cloud-init analyze blame Show slowest modules (like systemd-analyze blame)
cloud-init clean --logs Reset cloud-init state — next boot will re-run everything
cloud-init collect-logs Bundle all logs/config into a tarball for debugging
cloud-init single --name cc_write_files Re-run a single module (for testing)
netplan try Apply Netplan config with 120s auto-revert (safe for remote)
netplan generate Render Netplan YAML to backend config (dry run)
Solidnines — solidnines.com