LFCE 113 š§ iSCSI Target Setup
The iSCSI target is the server side of the block storage relationship. It exports LUNsālogical unit numbers backed by block devices, files, or RAM disksāand presents them to initiators over TCP port 3260. On modern Linux, this role is filled by LIO (LinuxIO), the in-kernel target subsystem, configured through the targetcli shell . LIO has been part of the kernel since the 2.6.38 era, and the userspace tooling has stabilized around the free-branch packages: targetcli-fb, python-rtslib-fb, and python-configshell-fb .
This chapter covers the complete target setup workflow: installing the tooling, creating backstores, defining a target IQN, attaching LUNs, configuring portals, restricting access with ACLs, enabling CHAP authentication, and persisting the configuration across reboots. By the end, you will have a target that an initiator can discover, log in to, and use as a block deviceāwith authentication enabled and the configuration surviving a restart.
Key point: targetcli writes directly to ConfigFS in the kernel. Changes take effect immediately, but they are lost on reboot unless saved with saveconfig and restored by the target service at boot. The save-and-enable step is not optional.
Why target setup has this shape
ConfigFS is the real configuration store. targetcli is a shell over ConfigFS, a kernel filesystem mounted at /sys/kernel/config. Every create command you issue creates a directory in ConfigFS, and the kernel reacts to that directory existing . This is why changes are immediate: there is no separate daemon to reload. It is also why saveconfig exists: ConfigFS is in-memory, and the saved JSON file at /etc/target/saveconfig.json is what the target service reads at boot to recreate the tree .
The backstore is separate from the target. A backstore is a storage object: a block device, a file, a RAM disk, or a passthrough SCSI device. It exists under /backstores/ and has no relationship to any target until you explicitly attach it as a LUN under a target’s TPG . This separation lets you create the storage once and map it to multiple targets or ACLs if needed.
The TPG is the configuration unit. When you create a target, targetcli automatically creates one Target Portal Group (TPG) named tpg1 . Almost all setups need only one. The TPG holds the LUNs, the portals (IP:port listeners), and the ACLs (initiator permissions). CHAP settings can live at the TPG level or per-ACL, and the generate_node_acls attribute decides which takes precedence .
ACLs are the authorization boundary. An ACL is created for a specific initiator IQN. Without an ACL, an initiator that authenticates may still see no LUNs, because the LUN mapping must exist under the ACL (or generate_node_acls must be enabled to fall back to TPG-wide mappings) . The safe default is generate_node_acls=0, which means only explicitly listed initiators get access .
a. Installing targetcli and starting the service
The package name varies by distribution, but the free-branch version is the maintained one. On RHEL-family systems it is targetcli; on Debian and Arch it is targetcli-fb .
# RHEL / CentOS / Oracle Linux
sudo dnf install targetcli
# Debian / Ubuntu
sudo apt install targetcli-fb
# Arch
sudo pacman -S targetcli-fb
After installation, the target service must be enabled. This service loads the necessary kernel modules (target_core_mod, iscsi_target_mod), mounts ConfigFS, and restores the saved configuration .
sudo systemctl enable --now target
On Debian, the unit may be named rtslib-fb-targetctl.service depending on the version . Check with systemctl list-unit-files | grep target if the standard name is not found.
b. Creating a backstore
A backstore is the actual storage. The two common types are block for block devices and fileio for file-backed LUNs. Block devices include physical disks, partitions, LVM volumes, and RAID arrays .
# Enter the targetcli shell
sudo targetcli
# Create a block backstore from a partition
/> cd /backstores/block
/backstores/block> create name=disk01 dev=/dev/sdb1
# Or create a file-backed backstore (200 MB image)
/> cd /backstores/fileio
/backstores/fileio> create name=file01 file_or_dev=/var/lib/iscsi/disk01.img size=200M
For file-backed storage, Red Hat recommends write_back=false to avoid data loss from the local filesystem cache . The block backstore type avoids that layer entirely by exporting the block device directly.
c. Creating the target and attaching LUNs
Creating a target generates an IQN automatically, or you can supply your own. The IQN follows the format iqn.YYYY-MM.reverse-domain:optional-suffix .
# Create a target with a generated IQN
/> cd /iscsi
/iscsi> create
Created target iqn.2024-01.com.example:storage.disk01
Created TPG 1.
# Or specify the IQN explicitly
/iscsi> create iqn.2024-01.com.example:storage.disk01
Attaching the backstore as a LUN creates the link between the storage object and the target. The LUN number is assigned automatically if you omit it .
# Navigate to the TPG's luns directory
/iscsi> cd iqn.2024-01.com.example:storage.disk01/tpg1/luns
/iscsi/.../tpg1/luns> create /backstores/block/disk01
Created LUN 0.
The target now has a LUN, but no portal and no ACL. An initiator cannot reach it yet.
d. Configuring portals and ACLs
A portal is an IP address and port the target listens on. When a target is created, targetcli automatically creates a default portal listening on all IPv4 and IPv6 addresses (::0) on port 3260 . In production, you typically replace this with a specific IP so the target does not listen on management or public interfaces.
# Navigate to portals
/iscsi/.../tpg1> cd portals
# Delete the default wildcard portal
/iscsi/.../tpg1/portals> delete 0.0.0.0 3260
# Create a portal on a specific storage-network IP
/iscsi/.../tpg1/portals> create 192.168.10.20 3260
The ACL lists the initiator IQNs allowed to connect. Without an ACL and with generate_node_acls=0, no initiator can log in, regardless of CHAP .
# Navigate to acls
/iscsi/.../tpg1> cd acls
# Create an ACL for the initiator's IQN
/iscsi/.../tpg1/acls> create iqn.2024-01.com.example:client01
Creating the ACL automatically maps the TPG’s LUNs to it. The initiator now sees LUN 0 when it logs in.
e. Enabling CHAP authentication
CHAP is enabled per-ACL or at the TPG level. The most common production configuration is one-way CHAP (target authenticates initiator) with generate_node_acls=0, so each ACL carries its own credentials.
# Navigate to the ACL
/iscsi/.../tpg1/acls> cd iqn.2024-01.com.example:client01
# Set the CHAP credentials the initiator must use
/iscsi/.../acls/iqn...client01> set auth userid=client01
/iscsi/.../acls/iqn...client01> set auth password=SecurePass123
For mutual CHAP, add the reverse credentials that the target will present to the initiator :
/iscsi/.../acls/iqn...client01> set auth mutual_userid=storage01
/iscsi/.../acls/iqn...client01> set auth mutual_password=TargetPass456
Ensure authentication is enabled on the TPG and ACL enforcement is active :
/iscsi/.../tpg1> set attribute authentication=1
/iscsi/.../tpg1> set attribute generate_node_acls=0
f. Saving and persisting the configuration
targetcli exits with an automatic save by default (auto_save_on_exit is true), but running saveconfig explicitly is harmless and removes ambiguity . The saved file is /etc/target/saveconfig.json .
# From the root of the targetcli tree
/> saveconfig
/> exit
The target service, which you enabled earlier, reads this file at boot. Verify it is enabled:
sudo systemctl is-enabled target
# Output: enabled
Because the saved file contains CHAP secrets in a recoverable form, restrict its permissions :
sudo chmod 600 /etc/target/saveconfig.json
Complete Example Session
# ============================================
# PART 1: INSTALL AND ENABLE THE SERVICE
# ============================================
sudo dnf install -y targetcli
sudo systemctl enable --now target
# Verify modules loaded and ConfigFS mounted
targetcli status
# ============================================
# PART 2: CREATE A BLOCK BACKSTORE
# ============================================
sudo targetcli
/> cd /backstores/block
/backstores/block> create name=disk01 dev=/dev/sdb1
# Output: Created block storage object disk01 using /dev/sdb1.
# ============================================
# PART 3: CREATE THE TARGET
# ============================================
/> cd /iscsi
/iscsi> create iqn.2024-01.com.example:storage.disk01
# Output: Created target iqn.2024-01.com.example:storage.disk01.
# Created TPG 1.
# ============================================
# PART 4: ATTACH THE LUN
# ============================================
/iscsi> cd iqn.2024-01.com.example:storage.disk01/tpg1/luns
/iscsi/.../tpg1/luns> create /backstores/block/disk01
# Output: Created LUN 0.
# ============================================
# PART 5: CONFIGURE A SPECIFIC PORTAL
# ============================================
/iscsi/.../tpg1/luns> cd ../portals
/iscsi/.../tpg1/portals> delete 0.0.0.0 3260
/iscsi/.../tpg1/portals> delete ::0 3260
/iscsi/.../tpg1/portals> create 192.168.10.20 3260
# Output: Created network portal 192.168.10.20:3260.
# ============================================
# PART 6: CREATE AN ACL FOR THE INITIATOR
# ============================================
/iscsi/.../tpg1/portals> cd ../acls
/iscsi/.../tpg1/acls> create iqn.2024-01.com.example:client01
# Output: Created Node ACL for iqn.2024-01.com.example:client01
# Created mapped LUN 0.
# ============================================
# PART 7: ENABLE ONE-WAY CHAP
# ============================================
/iscsi/.../tpg1/acls> cd iqn.2024-01.com.example:client01
/iscsi/.../acls/iqn...client01> set auth userid=client01
/iscsi/.../acls/iqn...client01> set auth password=SecurePass123
# ============================================
# PART 8: ENFORCE ACL-BASED AUTHENTICATION
# ============================================
/iscsi/.../acls/iqn...client01> cd ../..
/iscsi/.../tpg1> set attribute authentication=1
/iscsi/.../tpg1> set attribute generate_node_acls=0
# generate_node_acls=0 means ACLs are enforced;
# initiators without an ACL cannot log in.
# ============================================
# PART 9: SAVE AND EXIT
# ============================================
/iscsi/.../tpg1> cd /
/> saveconfig
# Output: Current configuration saved to /etc/target/saveconfig.json
/> exit
# ============================================
# PART 10: VERIFY PERSISTENCE AND PERMISSIONS
# ============================================
sudo systemctl is-enabled target
# Output: enabled
sudo chmod 600 /etc/target/saveconfig.json
sudo ls -l /etc/target/saveconfig.json
# Output: -rw-------. 1 root root ... saveconfig.json
# Optional: restart the service and confirm the target returns
sudo systemctl restart target
targetcli ls /iscsi
The ten parts covered installation and service enablement, backstore creation, target definition, LUN attachment, portal restriction, ACL creation, one-way CHAP, ACL enforcement, configuration persistence, and permission hardening.
Quick Reference
The targetcli object tree
| Path | Contents |
|---|---|
/backstores/block | Block device backstores |
/backstores/fileio | File-backed backstores |
/iscsi/<iqn>/tpg1/luns | LUN mappings for the TPG |
/iscsi/<iqn>/tpg1/portals | IP:port listeners |
/iscsi/<iqn>/tpg1/acls | Initiator ACLs |
The setup commands
| Command | Effect |
|---|---|
create name=X dev=/dev/sdX (in backstores) | Create block backstore |
create iqn... (in /iscsi) | Create target and TPG |
create /backstores/block/X (in luns) | Attach backstore as LUN |
create <IP> 3260 (in portals) | Listen on specific IP |
create iqn... (in acls) | Authorize an initiator |
set auth userid=... password=... (in ACL) | Set CHAP credentials |
set attribute authentication=1 (in TPG) | Enable CHAP enforcement |
saveconfig (at root) | Persist to /etc/target/saveconfig.json |
Authentication attributes
| Attribute | Location | Effect |
|---|---|---|
authentication=1 | TPG | Require CHAP on normal sessions |
generate_node_acls=0 | TPG | Enforce per-ACL mappings and CHAP |
generate_node_acls=1 | TPG | Ignore ACLs, use TPG-wide settings |
discovery_auth | /iscsi | Require CHAP for SendTargets |
Persistence files
| File | Purpose |
|---|---|
/etc/target/saveconfig.json | Saved configuration (contains secrets) |
target.service | Restores configuration at boot |
/sys/kernel/config/target | Live ConfigFS tree (in-memory) |
Best Practices
ā Do This:
# Set generate_node_acls=0 to enforce ACL mappings
set attribute generate_node_acls=0 # ā
# Restrict saveconfig.json permissions (contains CHAP secrets)
chmod 600 /etc/target/saveconfig.json # ā
# Replace the default wildcard portal with a specific storage-network IP
delete 0.0.0.0 3260 && create 192.168.10.20 3260 # ā
# Use a CHAP secret of at least 12 bytes
set auth password=SecurePass123 # ā
# Explicitly run saveconfig before exit
saveconfig # ā
# Verify the target service is enabled
systemctl is-enabled target # ā
ā Don’t Do This:
# Leave generate_node_acls=1 in production
set attribute generate_node_acls=1 # ā (ignores ACLs)
# Leave saveconfig.json world-readable
chmod 644 /etc/target/saveconfig.json # ā
# Use a short or trivial CHAP secret
set auth password=12345 # ā
# Assume ConfigFS changes survive reboot without saveconfig
# They do not; the kernel tree is in-memory
# exit without saveconfig # ā
# Listen on 0.0.0.0 on a host with public interfaces
create 0.0.0.0 3260 # ā (default portal)
# Skip ACL creation and rely on network isolation alone
# Any initiator that can reach port 3260 may attempt login
# (no ACLs) # ā
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Configuration lost after reboot | saveconfig not run or target service not enabled | Run saveconfig and systemctl enable target |
| Initiator logs in but sees no LUNs | LUN mapped under TPG but not under ACL, with generate_node_acls=0 | Create the ACL; LUN mapping is automatic, or map explicitly |
| Initiator cannot log in at all | No ACL for its IQN | Create ACL with the initiator’s IQN |
| CHAP fails despite correct password | authentication=0 on TPG, or wrong CHAP direction | Set authentication=1; verify one-way vs mutual |
| Target listens on unwanted interfaces | Default wildcard portal (::0) left in place | Delete it and create a specific IP portal |
targetcli changes vanish on exit | auto_save_on_exit disabled or not saved | Run saveconfig explicitly before exit |
Real-World Examples
1. Create a block backstore
/backstores/block> create name=disk01 dev=/dev/sdb1
2. Create a fileio backstore with write-through
/backstores/fileio> create name=file01 file_or_dev=/var/lib/iscsi/disk.img size=1G write_back=false
3. Create a target with an explicit IQN
/iscsi> create iqn.2024-01.com.example:storage.disk01
4. Attach a LUN
/iscsi/.../tpg1/luns> create /backstores/block/disk01
5. Replace the default portal
/iscsi/.../tpg1/portals> delete 0.0.0.0 3260
/iscsi/.../tpg1/portals> create 192.168.10.20 3260
6. Create an initiator ACL
/iscsi/.../tpg1/acls> create iqn.2024-01.com.example:client01
7. Set one-way CHAP
/iscsi/.../acls/iqn...client01> set auth userid=client01
/iscsi/.../acls/iqn...client01> set auth password=SecurePass123
8. Enforce ACLs and authentication
/iscsi/.../tpg1> set attribute authentication=1
/iscsi/.../tpg1> set attribute generate_node_acls=0
9. Save configuration
/> saveconfig
10. Verify persistence
systemctl is-enabled target && ls -l /etc/target/saveconfig.json
Visual
The targetcli object tree
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā TARGETCLI CONFIGURATION TREE ā
ā ā
ā / ā
ā āāā backstores/ ā
ā ā āāā block/ ā
ā ā ā āāā disk01 ā /dev/sdb1 ā
ā ā āāā fileio/ ā
ā ā āāā file01 ā /var/lib/iscsi/disk.img ā
ā ā ā
ā āāā iscsi/ ā
ā āāā iqn.2024-01.com.example:storage.disk01 ā
ā āāā tpg1/ ā
ā āāā luns/ ā
ā ā āāā lun0 ā /backstores/block/disk01 ā
ā āāā portals/ ā
ā ā āāā 192.168.10.20:3260 ā
ā āāā acls/ ā
ā āāā iqn.2024-01.com.example:client01 ā
ā āāā CHAP credentials ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
The access control layers
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ACCESS CONTROL LAYERS ā
ā ā
ā Layer 1: Network reachability ā
ā Can the initiator reach 192.168.10.20:3260? ā
ā ā
ā Layer 2: ACL existence ā
ā Is the initiator's IQN in the ACL list? ā
ā (enforced when generate_node_acls=0) ā
ā ā
ā Layer 3: CHAP authentication ā
ā Does the initiator know the correct password? ā
ā (enforced when authentication=1) ā
ā ā
ā Layer 4: LUN mapping ā
ā Which LUNs are mapped to this ACL? ā
ā ā
ā All four must pass for the initiator to see storage. ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Persistence flow
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā CONFIGFS TO PERSISTENCE ā
ā ā
ā targetcli create āāāŗ ConfigFS (/sys/kernel/config/target) ā
ā ā ā ā
ā ā ā¼ ā
ā ā Kernel applies immediately ā
ā ā ā
ā ā¼ ā
ā saveconfig āāāŗ /etc/target/saveconfig.json ā
ā ā ā
ā ā¼ ā
ā target.service at boot ā
ā ā ā
ā ā¼ ā
ā restoreconfig āāāŗ ConfigFS rebuilt ā
ā ā
ā Without saveconfig + enabled service, the target ā
ā disappears on reboot. ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
One-way vs mutual CHAP
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā CHAP DIRECTIONS ā
ā ā
ā One-way (most common): ā
ā Target āāchallengeāāāŗ Initiator ā
ā Target āāāresponseāāā Initiator ā
ā Target verifies initiator's password. ā
ā Initiator does not verify target. ā
ā ā
ā Mutual: ā
ā Target āāchallengeāāāŗ Initiator ā
ā Target āāāresponseāāā Initiator ā
ā Initiator āāchallengeāāāŗ Target ā
ā Initiator āāāresponseāāā Target ā
ā Both sides verify each other. ā
ā ā
ā Mutual requires mutual_userid and mutual_password ā
ā in addition to userid and password. ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Summary
| Item | Value |
|---|---|
| Target subsystem | LIO (LinuxIO), in-kernel since 2.6.38 |
| Configuration tool | targetcli (free-branch: targetcli-fb) |
| Backstore types | block (device), fileio (file), ramdisk, pscsi |
| Default iSCSI port | TCP 3260 |
| Target identity | IQN, created with create in /iscsi |
| TPG | Created automatically as tpg1 with each target |
| ACL | Per-initiator authorization, created by IQN |
| CHAP | set auth userid=... password=... in ACL |
| Enforcement | authentication=1, generate_node_acls=0 |
| Persistence file | /etc/target/saveconfig.json |
| Boot service | target.service (or rtslib-fb-targetctl.service) |
Key takeaways:
- ConfigFS is live, but not persistent. Every
targetclichange applies immediately to the kernel.saveconfigand an enabledtargetservice are what make it survive a reboot. - Backstores and targets are separate objects. Create the storage first, then attach it as a LUN under a specific TPG.
generate_node_acls=0is the production setting. It forces per-ACL LUN mapping and CHAP, so an initiator without an explicit ACL cannot log in.- The default portal listens on everything. Replace
::0with a specific storage-network IP to avoid exposing iSCSI on management or public interfaces. - CHAP credentials live in the ACL or the TPG. With
generate_node_acls=0, set them per-ACL. With=1, TPG-wide settings are used instead, which is acceptable only in test environments. - The saved configuration contains secrets. Restrict
/etc/target/saveconfig.jsonto mode 600.
Remember: iSCSI target setup is a sequence of four layers: storage (backstore), exposure (LUN under TPG), reachability (portal), and authorization (ACL plus CHAP). Each layer is independent in targetcli, and skipping one produces a target that exists but is unusable. The most common failure is an initiator that can reach the portal but sees no LUNs, because the LUN was attached to the TPG but no ACL was created for that initiator. Create the ACL, verify generate_node_acls=0, save the configuration, and enable the target service. That combination produces a target that works now and still works after the next reboot.
Stop using slow, ad-bloated tool sites! š¤®
š Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
ā
Finance (Mortgage, Interest, Inflation)
ā
Tech (Base64, JSON, Dev Suite, IP)
ā
Health (BMI, BMR, TDEE)
ā
Productivity (Timer, Workspace, QR)
ā”ļø Fast & Private
š No data leaves your device
š 100% Free
š Use it now: https://tools.kandz.me
š Bookmark itāyouāll need it later!