This guide provides instructions on how to configure NVMe over TCP storage on Proxmox VE using native CLI tools and connect it to your FlashArray.
-
For instructions on how to connect Proxmox to the FlashArray using FC, follow this guide.
-
For instructions on how to connect Proxmox to the FlashArray using NFS, follow this guide.
-
For instructions on how to connect Proxmox to the FlashArray using iSCSI, follow this guide.
Pre-requisites:
This guide assumes you already have the following:
- Proxmox VE 9.x or later
- FlashArray configured with NVMe-TCP network
- Portal IP address(es) and port
- Subsystem NQN
- Dedicated network interfaces for storage traffic (recommended)
- Network connectivity between Proxmox nodes and FlashArray
-
Configure network interfaces on all nodes:
Note: For optimal performance and reliability, configure dedicated network interfaces for NVMe-TCP traffic.
There are 2 options to configure network interfaces:
-
Option A: Dedicated Physical Interfaces (Recommended): Use separate physical NICs for storage traffic. No VLAN tagging, direct connection to storage network. Replace the interface names with your actual interface names. Replace the IPs with your actual host IP addresses.
Using the GUI:
-
Go to: Datacenter > <node> > System > Network, select the interface and click Edit.
-
Add the IP address.
-
Set the MTU to 9000 (or 1500 if jumbo frames are not configured on your network. This configuration is in the advanced section).
-
Enable Autostart.
-
Click OK.
Note: Gateway configuration should not be necessary. It is recommended that the interfaces are on the same subnet as the storage endpoints. If this is not the case manual routing will be necessary. -
Repeat the above steps for each interface used for NVMe-TCP traffic.
-
Click Apply Configuration.
-
Repeat the above steps for each node.
Using the CLI:
For each Proxmox node, edit
/etc/network/interfaces:# Storage interface 1 - dedicated physical NIC auto <INTERFACE_NAME_1> iface <INTERFACE_NAME_1> inet static address <PORTAL_IP_1>/<CIDR> mtu 9000 # Storage interface 2 - dedicated physical NIC auto <INTERFACE_NAME_2> iface <INTERFACE_NAME_2> inet static address <PORTAL_IP_2>/<CIDR> mtu 9000 -
-
Option B: VLAN Interfaces on Physical NICs: If you need to share physical NICs, use VLAN interfaces. This configuration can be applied directly to stand alone interfaces or to the physical interfaces in a bond. Replace the interface names with your actual interface names. Replace the IPs with your actual host IP addresses, and
100with your actual VLAN ID(s):Using the GUI:
-
Go to Datacenter > <node> > System > Network, select the interface and click Edit.
- If there is already an IP address assigned to the interface, leave it. This can be configured on interfaces that are part of a bond as well (these should already be active).
- Set the MTU to 9000 (or 1500 if jumbo frames are not configured on your network).
- Enable Autostart if it is not already enabled.
-
Click OK.
- Repeat the above steps for each interface.
-
Click on Create > Linux VLAN:
-
Name the new VLAN interface in the format of <physical_interface>.<vlan_id> (ens1f0np0.100 in the example below). This should match the name of the physical interface that the VLAN is being created on.
Note: The VLAN Tag field is not editable but shows the VLAN ID from the interface name. -
Add the IP address.
-
Set the MTU to 9000 (or 1500 if jumbo frames are not supported).
-
Enable Autostart.
-
Click Create.
-
Repeat for each physical interface and VLAN ID that needs to be configured.
-
Click Apply Configuration.
Using the CLI:
SSH to the Proxmox node and run the following commands:
-
Edit
/etc/network/interfaces:# Physical interface (no IP, just up) auto <INTERFACE_NAME_1> iface <INTERFACE_NAME_1> inet manual mtu 9000 # Storage VLAN 100 on <INTERFACE_NAME_1> auto <INTERFACE_NAME_1>.<VLAN_ID> iface <INTERFACE_NAME_1>.<VLAN_ID> inet static address <PORTAL_IP_1>/<CIDR> vlan-raw-device <INTERFACE_NAME_1> mtu 9000 # Physical interface 2 (no IP, just up) auto <INTERFACE_NAME_2> iface <INTERFACE_NAME_2> inet manual mtu 9000 # Storage VLAN 101 on <INTERFACE_NAME_2> auto <INTERFACE_NAME_2>.<VLAN_ID> iface <INTERFACE_NAME_2>.<VLAN_ID> inet static address <PORTAL_IP_2>/<CIDR> vlan-raw-device <INTERFACE_NAME_2> mtu 9000 -
Apply the configuration:
# Apply changes ifreload -a # Verify interfaces are up ip addr show <INTERFACE_NAME_1>.<VLAN_ID> ip addr show <INTERFACE_NAME_2>.<VLAN_ID> # Test connectivity to storage portals ping -I <INTERFACE_NAME_1> <PORTAL_IP_1> ping -I <INTERFACE_NAME_1> <PORTAL_IP_2> ping -I <INTERFACE_NAME_2> <PORTAL_IP_1> ping -I <INTERFACE_NAME_2> <PORTAL_IP_2> -
Repeat on each node.
-
Note:Design Considerations:
- Jumbo frames (MTU 9000): Recommended for storage traffic. Ensure switches and storage support jumbo frames end-to-end.
- Same Subnet: This configuration assumes the same subnet. You can use multiple subnets if needed.
- No bonding: Do not use a bond interface for storage. If the interfaces are part of a bond, the interface name or raw device setting will apply the configuration to the specific interface.
- No Routing Of Storage Traffic: Ensure the interfaces are on the same subnet as the storage endpoints. If this is not the case manual routing will be necessary.
-
-
Install dependencies on all Proxmox nodes by running the following commands on every node:
# Install nvme-cli apt-get update apt-get install -y nvme-cli # Load kernel modules modprobe nvme modprobe nvme-tcp modprobe nvme-core # Make modules load on boot cat > /etc/modules-load.d/nvme-tcp.conf << 'EOF' nvme nvme-tcp nvme-core EOF # Make multipath persistent across reboots echo 'options nvme_core multipath=Y' > /etc/modprobe.d/nvme-tcp.conf -
Generate host NQN on all nodes:
# Create directory mkdir -p /etc/nvme # Generate host NQN if it doesn't exist if [ ! -f /etc/nvme/hostnqn ]; then nvme gen-hostnqn > /etc/nvme/hostnqn fi # Display host NQN - register this with your storage array cat /etc/nvme/hostnqn -
Use the NQN generated to create host objects in the Pure Storage Purity//FA and create a volume:
-
On each Proxmox host, run the command
cat /etc/nvme/hostnqnand copy the NQN of the host so that you can map it to the host on the array:Example:
[root@init20-13 ~]# cat /etc/nvme/hostnqn nqn.2014-08.org.nvmexpress:uuid:f3595813-be97-4277-b814-17faee1bcabc [root@init20-13 ~]# -
Use this NQN to create host objects in the Pure Storage Purity//FA management interface:
-
Go to Storage > Hosts.
-
Click the + icon.
-
In the pop-up window, give your host a name and under personality select none.
-
Add the Proxmox host NQN under the Host Ports section.
-
-
Create a Host Group for the Proxmox "cluster" and add the hosts to the host group.
-
Go to Storage > Hosts.
-
Click the + icon.
-
Click Create Host Group. In the pop-up window, give your host group a name.
-
Add the Proxmox hosts to the Host Group.
-
-
Create a volume/LUN for your host:
-
Go to Storage > Volumes.
-
Click the + icon.
-
In the pop-up window, give your volume a name, volume size and add your newly created host group to this volume/LUN.
-
-
On each Proxmox host, run the command
-
Optional: Discover targets:
Replace the
<PORTAL_IP_X>with your actual FlashArray NVMe-TCP portal addresses. Replace<INTERFACE_NAME>with your actual Proxmox interface name. Replace<INTERFACE_IP>with the Proxmox node IP address of the interface:# Discover available NVMe subsystems via specific interface nvme discover -t tcp \ -a <PORTAL_IP_1> \ -s 8009 \ --host-iface=<INTERFACE_NAME > \ --host-traddr=<INTERFACE_IP> -
Create a
udevrule on all nodes to automatically set the IO policy when NVMe subsystems are created:Note: The udev rule ensures the IO policy is set automatically on every boot and whenever new subsystems are connected.# Create udev rule for NVMe IO policy cat > /etc/udev/rules.d/99-nvme-iopolicy.rules << 'EOF' # Set IO policy to queue-depth for all NVMe subsystems ACTION=="add", SUBSYSTEM=="nvme-subsystem", ATTR{iopolicy}="queue-depth" EOF # Reload udev rules udevadm control --reload-rules udevadm trigger # Apply to existing subsystems (for current session) for subsys in /sys/class/nvme-subsystem/nvme-subsys*; do echo "queue-depth" > "$subsys/iopolicy" 2>/dev/null || true done -
Connect every Proxmox node to the NVMe-TCP Target:
Each Proxmox interface connects to ALL FlashArray portals for maximum path redundancy. With 2 source interfaces and 4 target portals, you get 8 paths total. Replace
<FLASHARRAY_IP_X>with your actual portal IP addresses, and<SUBSYSTEM_NQN>with your actual subsystem (FlashArray) NQN. Replace<INTERFACE_NAME_1>and<INTERFACE_NAME_2>with your actual interface names. Replace<INTERFACE_IP_1>and<INTERFACE_IP_2>with your actual host IP addresses. Replace 4420 with your actual port if different:Note:-
The example below assumes you have 4 portals. If you have more or less, adjust accordingly.
-
Depending on your network configuration, the below commands may vary.
# Interface 1 -> All Portals nvme connect -t tcp -a <FLASHARRAY_IP_1> -s 4420 -n <SUBSYSTEM_NQN> \ --host-iface=<INTERFACE_NAME_1> --host-traddr=<INTERFACE_IP_1> \ --ctrl-loss-tmo=1800 --reconnect-delay=10 nvme connect -t tcp -a <FLASHARRAY_IP_2> -s 4420 -n <SUBSYSTEM_NQN> \ --host-iface=<INTERFACE_NAME_1> --host-traddr=<INTERFACE_IP_1> \ --ctrl-loss-tmo=1800 --reconnect-delay=10 nvme connect -t tcp -a <FLASHARRAY_IP_3> -s 4420 -n <SUBSYSTEM_NQN> \ --host-iface=<INTERFACE_NAME_1> --host-traddr=<INTERFACE_IP_1> \ --ctrl-loss-tmo=1800 --reconnect-delay=10 nvme connect -t tcp -a <FLASHARRAY_IP_4> -s 4420 -n <SUBSYSTEM_NQN> \ --host-iface=<INTERFACE_NAME_1> --host-traddr=<INTERFACE_IP_1> \ --ctrl-loss-tmo=1800 --reconnect-delay=10 # Interface 2 -> All Portals nvme connect -t tcp -a <FLASHARRAY_IP_1> -s 4420 -n <SUBSYSTEM_NQN> \ --host-iface=<INTERFACE_NAME_2> --host-traddr=<INTERFACE_IP_2> \ --ctrl-loss-tmo=1800 --reconnect-delay=10 nvme connect -t tcp -a <FLASHARRAY_IP_2> -s 4420 -n <SUBSYSTEM_NQN> \ --host-iface=<INTERFACE_NAME_2> --host-traddr=<INTERFACE_IP_2> \ --ctrl-loss-tmo=1800 --reconnect-delay=10 nvme connect -t tcp -a <FLASHARRAY_IP_3> -s 4420 -n <SUBSYSTEM_NQN> \ --host-iface=<INTERFACE_NAME_2> --host-traddr=<INTERFACE_IP_2> \ --ctrl-loss-tmo=1800 --reconnect-delay=10 nvme connect -t tcp -a <FLASHARRAY_IP_4> -s 4420 -n <SUBSYSTEM_NQN> \ --host-iface=<INTERFACE_NAME_2> --host-traddr=<INTERFACE_IP_2> \ --ctrl-loss-tmo=1800 --reconnect-delay=10Verify the connection:
# List subsystems and paths (should show 8 paths) nvme list-subsys # List NVMe devices nvme list # Check block devices lsblk | grep nvme # Verify cat /sys/class/nvme-subsystem/nvme-subsys*/iopolicy -
-
Configure persistent connections on all nodes by using the native nvme-cli discovery configuration for automatic connections on boot. Each interface connects to all portals for maximum redundancy (2 interfaces × 4 portals = 8 paths):
-
Option A: Discovery Controller Configuration (Recommended):
-
Create a discovery configuration file:
# Create config directory mkdir -p /etc/nvme # Create discovery.conf - one entry per path (interface + portal combination) # 2 interfaces x 4 portals = 8 entries cat > /etc/nvme/discovery.conf << 'EOF' # NVMe-TCP Discovery Configuration # Each line defines one path: host interface -> storage portal # Format: --transport=tcp --traddr=<portal> --trsvcid=<port> --host-iface=<iface> --host-traddr=<host-ip> # Interface 1 -> All Portals --transport=tcp --traddr=<PORTAL_IP_1> --trsvcid=4420 --host-iface=<INTERFACE_NAME_1> --host-traddr=<INTERFACE_IP_1> --transport=tcp --traddr=<PORTAL_IP_2> --trsvcid=4420 --host-iface=<INTERFACE_NAME_1> --host-traddr=<INTERFACE_IP_1> --transport=tcp --traddr=<PORTAL_IP_3> --trsvcid=4420 --host-iface=<INTERFACE_NAME_1> --host-traddr=<INTERFACE_IP_1> --transport=tcp --traddr=<PORTAL_IP_4> --trsvcid=4420 --host-iface=<INTERFACE_NAME_1> --host-traddr=<INTERFACE_IP_1> # Interface 2 -> All Portals --transport=tcp --traddr=<PORTAL_IP_1> --trsvcid=4420 --host-iface=<INTERFACE_NAME_2> --host-traddr=<INTERFACE_IP_2> --transport=tcp --traddr=<PORTAL_IP_2> --trsvcid=4420 --host-iface=<INTERFACE_NAME_2> --host-traddr=<INTERFACE_IP_2> --transport=tcp --traddr=<PORTAL_IP_3> --trsvcid=4420 --host-iface=<INTERFACE_NAME_2> --host-traddr=<INTERFACE_IP_2> --transport=tcp --traddr=<PORTAL_IP_4> --trsvcid=4420 --host-iface=<INTERFACE_NAME_2> --host-traddr=<INTERFACE_IP_2> EOF -
Enable and start the nvme-connect service:
# Enable automatic connection on boot systemctl enable nvmf-autoconnect.service # Connect all (discovers and connects to all subsystems via all paths.) nvme connect-all
-
-
Option B: Per-Subsystem Configuration: For more control, create individual configuration files with explicit NQN and timeouts:
# Create config directory for subsystems mkdir -p /etc/nvme/config.d # Create a config file for your subsystem # 2 interfaces x 4 portals = 8 paths cat > /etc/nvme/config.d/my-storage.conf << 'EOF' # Subsystem: my-storage # 8 paths: 2 interfaces x 4 portals # Interface 1 -> All Portals --transport=tcp --traddr=<PORTAL_IP_1> --trsvcid=4420 --host-iface=<INTERFACE_NAME_1> --host-traddr=<INTERFACE_IP_1> --nqn=<SUBSYSTEM_NQN> --ctrl-loss-tmo=1800 --reconnect-delay=10 --transport=tcp --traddr=<PORTAL_IP_2> --trsvcid=4420 --host-iface=<INTERFACE_NAME_1> --host-traddr=<INTERFACE_IP_1> --nqn=<SUBSYSTEM_NQN> --ctrl-loss-tmo=1800 --reconnect-delay=10 --transport=tcp --traddr=<PORTAL_IP_3> --trsvcid=4420 --host-iface=<INTERFACE_NAME_1> --host-traddr=<INTERFACE_IP_1> --nqn=<SUBSYSTEM_NQN> --ctrl-loss-tmo=1800 --reconnect-delay=10 --transport=tcp --traddr=<PORTAL_IP_4> --trsvcid=4420 --host-iface=<INTERFACE_NAME_1> --host-traddr=<INTERFACE_IP_1> --nqn=<SUBSYSTEM_NQN> --ctrl-loss-tmo=1800 --reconnect-delay=10 # Interface 2 -> All Portals --transport=tcp --traddr=<PORTAL_IP_1> --trsvcid=4420 --host-iface=<INTERFACE_NAME_2> --host-traddr=<INTERFACE_IP_2> --nqn=<SUBSYSTEM_NQN> --ctrl-loss-tmo=1800 --reconnect-delay=10 --transport=tcp --traddr=<PORTAL_IP_2> --trsvcid=4420 --host-iface=<INTERFACE_NAME_2> --host-traddr=<INTERFACE_IP_2> --nqn=<SUBSYSTEM_NQN> --ctrl-loss-tmo=1800 --reconnect-delay=10 --transport=tcp --traddr=<PORTAL_IP_3> --trsvcid=4420 --host-iface=<INTERFACE_NAME_2> --host-traddr=<INTERFACE_IP_2> --nqn=<SUBSYSTEM_NQN> --ctrl-loss-tmo=1800 --reconnect-delay=10 --transport=tcp --traddr=<PORTAL_IP_4> --trsvcid=8009 --host-iface=<INTERFACE_NAME_2> --host-traddr=<INTERFACE_IP_2> --nqn=<SUBSYSTEM_NQN> --ctrl-loss-tmo=1800 --reconnect-delay=10 4420 # Enable automatic connection systemctl enable nvmf-autoconnect.service # Connect all nvme connect-all
Verify the persistent configuration:
Warning: If you will test by rebooting your host, make sure that there are no active workloads running on the node.# Check if service is enabled systemctl is-enabled nvmf-autoconnect.service # View current connections nvme list-subsys # Test by rebooting rebootAfter the reboot, verify that the connections have been restored:
nvme list-subsys nvme list -
-
Create an LVM volume group on one node as seen below. This guide assumes multi-node access and uses LVM thick as the only supported method for usage. See Storage - Proxmox VE for supported tolopolgies.
Using the GUI:
-
Go to Datacenter > <node> > Disks and make sure the NVMe device is visible. If it is not, click Reload:
-
Go to LVM and create a new volume group with the new device:
-
Select the Disk (NVMe device) and add a Name to the volume group. Ensure Add Storage is checked and click Create:
The new volume group should now be visible in the storage list. The shared configuration is not yet enabled and the storage is not yet available to the other nodes.
-
Go to: Datacenter > Storage, select the volume group and click Edit.
-
Check the Shared box. Enable the volume on other nodes by either selecting them in the Nodes drop down or by clearing the Nodes field by clicking the x to the right of the field. Click OK:
The storage should now be enabled on all nodes or the selected nodes.
Tip:If a node has a ? overlay on the icon for the volume or the status says unknown on a node, SSH to the node and run the following command:
pvscan --cacheRepeat for all nodes that have this status. This should update the nodes cache of LVM volumes and the storage should now be available.
Using the CLI:
Run the following commands on one node only:
# List NVMe namespaces lsblk | grep nvme # Create LVM physical volume (use your device) pvcreate /dev/nvme0n1 # Create volume group vgcreate nvme-vg /dev/nvme0n1 # Verify vgs pvsAdd LVM Storage to Proxmox on one node only (configuration synchronizes via the cluster):
# Add LVM storage pvesm add lvm nvme-datastore \ --vgname nvme-vg \ --content images,rootdir \ --shared 1 # Verify pvesm statusActivate the volume group by running these commands on each of the other nodes:
pvscan --cacheVerify the setup on all nodes:
# Check NVMe connections nvme list-subsys # Check IO policy cat /sys/class/nvme-subsystem/nvme-subsys*/iopolicy # Check Proxmox storage pvesm status # Check LVM vgs lvs -
| Term | Definition |
| Portal | A specific network endpoint, comprising of an IP address and a TCP port number on an NVMe storage target (subsystem) where an NVMe host (initiator) connects to access storage namespaces. An example would be an interface on a controller, like the IP address eth15 (Ethernet port 15) on CT0 (Controller 0). |
| Command | Description |
|---|---|
nvme list
|
List NVMe devices |
nvme list-subsys
|
List subsystems and paths |
nvme discover -t tcp -a <ip> -s 8009
|
Discover targets |
nvme connect -t tcp -a <ip> -n <nqn>
|
Connect to target |
nvme disconnect -n <nqn>
|
Disconnect from target / subsystem |
cat /sys/class/nvme-subsystem/*/iopolicy
|
Check IO policy |
nvme disconnect-all
|
Disconnect from all targets |