Frequently Asked Question
Ansible can be used with Proxmox VE 9.2 in two main ways:
- Configuration management on the Proxmox hosts themselves
- API-driven automation against the Proxmox cluster
This allows playbooks to both:
- enforce consistent host configuration
- create or modify VMs and containers
- query cluster state
- make decisions dynamically based on API responses
A common pattern is:
- use Ansible over SSH to configure the Proxmox nodes
- use the Proxmox API from Ansible to inspect or change virtual infrastructure
- use the returned data in
whenconditions, loops, and facts
Typical use cases
Ansible is well suited to tasks such as:
- configuring repositories, networking, NTP, sysctl settings, and storage on Proxmox hosts
- ensuring packages are installed consistently across all nodes
- creating VM or LXC guests from templates
- starting, stopping, or migrating guests
- checking whether a VM already exists before creating it
- choosing a target node based on free memory, CPU, or storage
- querying snapshots, backup status, or storage content before taking action
Prerequisites
For most deployments, the following are required:
- SSH access to the Proxmox nodes for host configuration
- a Proxmox API token for API-based automation
- Python available on the Ansible control host
- the relevant Ansible collection installed
Install the community collection commonly used for Proxmox automation:
ansible-galaxy collection install community.general
Depending on the modules used, the control host may also need Python libraries such as:
pip install proxmoxer requests
Authentication to the Proxmox API
For automation, API tokens are preferable to username/password authentication because they are easier to scope and safer for unattended use.
A typical token-based variable set in Ansible looks like this:
proxmox_api_host: "pve01.example.internal"
proxmox_api_user: "automation@pve"
proxmox_api_token_id: "ansible"
proxmox_api_token_secret: "REDACTED_SECRET"
proxmox_validate_certs: false
Store secrets in Ansible Vault rather than in plain text.
Example vaulted variable file creation:
ansible-vault create group_vars/proxmox/vault.yml
Example vaulted content:
vault_proxmox_api_token_secret: "REDACTED_SECRET"
Inventory example
A simple inventory might separate Proxmox nodes from other systems:
[proxmox]
pve01 ansible_host=192.0.2.10
pve02 ansible_host=192.0.2.11
pve03 ansible_host=192.0.2.12
[proxmox:vars]
ansible_user=root
Using Ansible to configure Proxmox hosts
This is standard configuration management over SSH. For example, to ensure useful packages and NTP are configured:
- name: Configure Proxmox hosts
hosts: proxmox
become: true
tasks:
- name: Ensure common packages are installed
ansible.builtin.apt:
name:
- vim
- curl
- jq
- chrony
state: present
update_cache: true
- name: Ensure chrony is enabled
ansible.builtin.service:
name: chrony
enabled: true
state: started
This approach is best for:
- package installation
- file deployment
- kernel parameters
- service state
- cluster node baseline configuration
Using Ansible to call Proxmox API functions
For infrastructure actions, Ansible can call the Proxmox API through modules or directly via HTTP.
There are two common methods:
- Ansible modules such as
community.general.proxmox_kvm - Generic API calls using
ansible.builtin.uri
Using modules is usually simpler for guest lifecycle operations. Using uri is useful when a Proxmox API function is not covered cleanly by a module.
Example 1: Create a VM only if it does not already exist
This example uses the API to list VMs on a node, then creates one only when the target VMID is absent.
- name: Create VM only if missing
hosts: localhost
gather_facts: false
vars:
proxmox_api_host: "pve01.example.internal"
proxmox_api_user: "automation@pve"
proxmox_api_token_id: "ansible"
proxmox_api_token_secret: "{{ vault_proxmox_api_token_secret }}"
proxmox_validate_certs: false
target_node: "pve01"
target_vmid: 120
tasks:
- name: Query VMs on target node
ansible.builtin.uri:
url: "https://{{ proxmox_api_host }}:8006/api2/json/nodes/{{ target_node }}/qemu"
method: GET
validate_certs: "{{ proxmox_validate_certs }}"
headers:
Authorization: "PVEAPIToken={{ proxmox_api_user }}!{{ proxmox_api_token_id }}={{ proxmox_api_token_secret }}"
register: proxmox_vms
- name: Build list of existing VMIDs
ansible.builtin.set_fact:
existing_vmids: "{{ proxmox_vms.json.data | map(attribute='vmid') | list }}"
- name: Create VM if it does not exist
community.general.proxmox_kvm:
api_host: "{{ proxmox_api_host }}"
api_user: "{{ proxmox_api_user }}"
api_token_id: "{{ proxmox_api_token_id }}"
api_token_secret: "{{ proxmox_api_token_secret }}"
validate_certs: "{{ proxmox_validate_certs }}"
node: "{{ target_node }}"
vmid: "{{ target_vmid }}"
name: "app01"
memory: 4096
cores: 2
sockets: 1
net:
net0: "virtio,bridge=vmbr0"
scsihw: "virtio-scsi-pci"
state: present
when: target_vmid not in existing_vmids
What this does
- calls the Proxmox API endpoint for QEMU guests on a node
- extracts the existing
vmidvalues - creates the VM only if the requested
vmidis not present
This is a good example of using API data to make playbook decisions.
Example 2: Choose a node based on available memory
A common requirement in clustered environments is to place a new VM on the node with the most free RAM.
- name: Select best node by free memory
hosts: localhost
gather_facts: false
vars:
proxmox_api_host: "pve01.example.internal"
proxmox_api_user: "automation@pve"
proxmox_api_token_id: "ansible"
proxmox_api_token_secret: "{{ vault_proxmox_api_token_secret }}"
proxmox_validate_certs: false
tasks:
- name: Query cluster resource summary
ansible.builtin.uri:
url: "https://{{ proxmox_api_host }}:8006/api2/json/cluster/resources?type=node"
method: GET
validate_certs: "{{ proxmox_validate_certs }}"
headers:
Authorization: "PVEAPIToken={{ proxmox_api_user }}!{{ proxmox_api_token_id }}={{ proxmox_api_token_secret }}"
register: cluster_nodes
- name: Build list of online nodes with calculated free memory
ansible.builtin.set_fact:
candidate_nodes: >-
{{
cluster_nodes.json.data
| selectattr('status', 'equalto', 'online')
| map('combine', {'free_mem': 0})
| list
}}
- name: Recalculate free memory per node
ansible.builtin.set_fact:
candidate_nodes: "{{ recalculated_nodes }}"
vars:
recalculated_nodes: >-
{%- set result = [] -%}
{%- for n in cluster_nodes.json.data if n.status == 'online' -%}
{%- set _ = result.append(n | combine({'free_mem': (n.maxmem | int) - (n.mem | int)})) -%}
{%- endfor -%}
{{ result }}
- name: Select node with most free memory
ansible.builtin.set_fact:
selected_node: "{{ (candidate_nodes | sort(attribute='free_mem') | last).node }}"
- name: Show selected node
ansible.builtin.debug:
msg: "Selected node: {{ selected_node }}"
What this does
- queries node resource data from the cluster API
- filters to online nodes
- calculates free memory for each node
- selects the node with the highest free memory
This selected node can then be used in later tasks for VM creation.
Example 3: Clone a VM template to the selected node
This combines decision-making with an actual provisioning task.
- name: Clone template to best node
hosts: localhost
gather_facts: false
vars:
proxmox_api_host: "pve01.example.internal"
proxmox_api_user: "automation@pve"
proxmox_api_token_id: "ansible"
proxmox_api_token_secret: "{{ vault_proxmox_api_token_secret }}"
proxmox_validate_certs: false
template_node: "pve01"
template_vmid: 9000
new_vmid: 130
new_vm_name: "web01"
selected_node: "pve02"
tasks:
- name: Clone from template
community.general.proxmox_kvm:
api_host: "{{ proxmox_api_host }}"
api_user: "{{ proxmox_api_user }}"
api_token_id: "{{ proxmox_api_token_id }}"
api_token_secret: "{{ proxmox_api_token_secret }}"
validate_certs: "{{ proxmox_validate_certs }}"
node: "{{ template_node }}"
clone: "{{ template_vmid }}"
newid: "{{ new_vmid }}"
name: "{{ new_vm_name }}"
target: "{{ selected_node }}"
full: true
state: present
Notes
nodeis the node where the template currently existstargetis the destination node for the clone- this is a common pattern for golden-image deployments
Example 4: Query snapshots and act only if a named snapshot exists
Some workflows need to inspect guest state before acting.
- name: Check whether a VM snapshot exists
hosts: localhost
gather_facts: false
vars:
proxmox_api_host: "pve01.example.internal"
proxmox_api_user: "automation@pve"
proxmox_api_token_id: "ansible"
proxmox_api_token_secret: "{{ vault_proxmox_api_token_secret }}"
proxmox_validate_certs: false
target_node: "pve01"
target_vmid: 120
snapshot_name: "pre-maintenance"
tasks:
- name: Query snapshots
ansible.builtin.uri:
url: "https://{{ proxmox_api_host }}:8006/api2/json/nodes/{{ target_node }}/qemu/{{ target_vmid }}/snapshot"
method: GET
validate_certs: "{{ proxmox_validate_certs }}"
headers:
Authorization: "PVEAPIToken={{ proxmox_api_user }}!{{ proxmox_api_token_id }}={{ proxmox_api_token_secret }}"
register: snapshot_info
- name: Extract snapshot names
ansible.builtin.set_fact:
snapshot_names: "{{ snapshot_info.json.data | map(attribute='name') | list }}"
- name: Stop VM only if snapshot exists
community.general.proxmox_kvm:
api_host: "{{ proxmox_api_host }}"
api_user: "{{ proxmox_api_user }}"
api_token_id: "{{ proxmox_api_token_id }}"
api_token_secret: "{{ proxmox_api_token_secret }}"
validate_certs: "{{ proxmox_validate_certs }}"
node: "{{ target_node }}"
vmid: "{{ target_vmid }}"
state: stopped
when: snapshot_name in snapshot_names
Why this is useful
This avoids blind actions and allows playbooks to behave conditionally based on actual cluster state.
Example 5: Use raw API endpoints for unsupported or very specific functions
Not every Proxmox API function has a perfect Ansible module wrapper. In those cases, uri is often the simplest option.
For example, querying storage content:
- name: Query storage content
hosts: localhost
gather_facts: false
vars:
proxmox_api_host: "pve01.example.internal"
proxmox_api_user: "automation@pve"
proxmox_api_token_id: "ansible"
proxmox_api_token_secret: "{{ vault_proxmox_api_token_secret }}"
proxmox_validate_certs: false
target_node: "pve01"
storage_name: "local-lvm"
tasks:
- name: Get storage content
ansible.builtin.uri:
url: "https://{{ proxmox_api_host }}:8006/api2/json/nodes/{{ target_node }}/storage/{{ storage_name }}/content"
method: GET
validate_certs: "{{ proxmox_validate_certs }}"
headers:
Authorization: "PVEAPIToken={{ proxmox_api_user }}!{{ proxmox_api_token_id }}={{ proxmox_api_token_secret }}"
register: storage_content
- name: Show ISO and disk images
ansible.builtin.debug:
var: storage_content.json.data
This pattern is useful when:
- retrieving detailed cluster data
- using newer API features before module support catches up
- building custom logic around Proxmox resources
Common design pattern for Proxmox automation
A robust Ansible workflow for Proxmox 9.2 often looks like this:
- Gather state
- query nodes, storage, guests, snapshots, or backups via API
- Set facts
- build lists such as available nodes, used VMIDs, or storage utilisation
- Make decisions
- use
when,failed_when, and calculated variables
- Apply changes
- create, clone, start, stop, migrate, or reconfigure resources
- Verify
- query the API again to confirm the expected result
Practical examples of decisions in playbooks
Examples of conditions driven by API data include:
- create a VM only when the VMID is unused
- place a VM only on nodes with more than a defined amount of free RAM
- skip backup-related actions if no snapshot or backup exists
- migrate workloads away from a node marked offline or in maintenance
- choose storage with enough free capacity before cloning a disk
- start a VM only if it is not already running
Example conditional expression:
when:
- selected_node is defined
- free_gb | int > 100
- vm_status != "running"
Working with Proxmox host configuration and API automation together
A single Ansible project can contain both node configuration and virtual infrastructure tasks.
A common structure is:
inventory/
group_vars/
host_vars/
roles/
proxmox_host_baseline/
proxmox_network/
proxmox_storage/
proxmox_vm_provision/
site.yml
For example:
proxmoxhostbaselineconfigures the Proxmox nodes over SSHproxmoxvmprovisiontalks to the API fromlocalhost
This keeps the separation clear:
- SSH tasks manage the Linux host
- API tasks manage Proxmox objects
Important considerations for Proxmox VE 9.2
When automating Proxmox 9.2, keep the following in mind:
- test API calls against the live API viewer on the cluster
- token permissions must match the actions being performed
- many API operations are asynchronous, so follow-up polling may be needed
- if using self-signed certificates, set certificate validation deliberately rather than ignoring it by default
- be careful with cluster-wide operations such as migration, HA, and storage changes
- module support in Ansible collections may lag behind the newest Proxmox features, so
uriremains important
Example of polling an asynchronous task
Some API operations return a task identifier rather than completing immediately. A polling pattern can be used.
- name: Start VM and wait for completion task
hosts: localhost
gather_facts: false
vars:
proxmox_api_host: "pve01.example.internal"
proxmox_api_user: "automation@pve"
proxmox_api_token_id: "ansible"
proxmox_api_token_secret: "{{ vault_proxmox_api_token_secret }}"
proxmox_validate_certs: false
target_node: "pve01"
target_vmid: 120
tasks:
- name: Start VM
ansible.builtin.uri:
url: "https://{{ proxmox_api_host }}:8006/api2/json/nodes/{{ target_node }}/qemu/{{ target_vmid }}/status/start"
method: POST
validate_certs: "{{ proxmox_validate_certs }}"
headers:
Authorization: "PVEAPIToken={{ proxmox_api_user }}!{{ proxmox_api_token_id }}={{ proxmox_api_token_secret }}"
register: start_task
- name: Poll task status
ansible.builtin.uri:
url: "https://{{ proxmox_api_host }}:8006/api2/json/nodes/{{ target_node }}/tasks/{{ start_task.json.data }}/status"
method: GET
validate_certs: "{{ proxmox_validate_certs }}"
headers:
Authorization: "PVEAPIToken={{ proxmox_api_user }}!{{ proxmox_api_token_id }}={{ proxmox_api_token_secret }}"
register: task_status
retries: 20
delay: 3
until: task_status.json.data.status == "stopped"
Best practice summary
- use Ansible over SSH for Proxmox host configuration
- use API tokens for infrastructure automation
- prefer Ansible modules for standard VM and container tasks
- use
ansible.builtin.urifor advanced or less common API functions - base decisions on queried API data rather than assumptions
- keep secrets in Ansible Vault
- test carefully in a non-production cluster first
In summary
Ansible can leverage Proxmox VE 9.2 very effectively by combining:
- host configuration management
- API-driven orchestration
- conditional logic based on live cluster state
This makes it possible to build playbooks that do more than just push settings. They can inspect Proxmox, evaluate conditions such as node health, free memory, storage availability, VM existence, or snapshot presence, and then take the correct action automatically.
