Automated staged software upgrade tool for Juniper EX, MX, and SRX platforms.
Installs software to the pending partition (no reboot) — reboot is performed separately during a maintenance window.
Use this tool at your own risk.
This utility automates software upgrades on live network infrastructure. Improper use, untested configurations, or unexpected device behavior can result in network outages, devices failing to boot, or loss of connectivity to managed equipment.
Before using this tool on any production network:
Run dry-runs first — always validate with
--dry-runbefore touching any device. Confirm the upgrade paths, package files, and device connectivity all look correct.Test on a single non-critical device — run a real upgrade on one lab or low-impact device first. Observe the full output, verify the device reboots cleanly on the new version, and confirm network behavior is as expected before upgrading anything else.
Understand your environment — behavior may vary across software versions, hardware revisions, configurations, and network topologies. What works in a lab may behave differently in production. Know your devices before you run this at scale.
Have a rollback plan — the script issues
request system software rollbackautomatically on failure, but this does not reboot the device. Ensure you have out-of-band console access or a maintenance window where a manual reboot is acceptable.Parallel mode carries higher risk — upgrading many devices simultaneously means a misconfiguration or unexpected error affects multiple devices at once. Use sequential mode until you are confident the tool behaves correctly in your environment.
No warranty is provided. This tool is offered as-is. The authors accept no responsibility for outages, data loss, or any other damage resulting from its use.
| Platform | Junos Model String (examples) | Package Filename |
|---|---|---|
| EX2300-C | ex2300-c-12p, ex2300-c-12t | junos-arm-32-{version}.tgz |
| EX2300 | ex2300-24p, ex2300-48p | junos-arm-32-{version}.tgz |
| EX3400 | ex3400-24p, ex3400-48p | junos-arm-32-{version}.tgz |
| EX4100 | ex4100-24p, ex4100-48p, ex4100-h-* | junos-install-ex-arm-64-{version}.tgz |
| EX4600 | ex4600-40f | jinstall-host-ex-4600-{version}-signed.tgz |
| EX4650 | ex4650-48y | jinstall-host-ex-4e-x86-64-{version}-secure-signed.tgz |
| MX204 | mx204 | junos-vmhost-install-mx-x86-64-{version}.tgz |
| SRX300 | srx300, srx320, srx340, srx345 | junos-srxsme-{version}.tgz |
EX4100-H note: The EX4100-H variant is currently mapped to the same EX4100 package.
Verify compatibility before upgrading — updatePLATFORM_MAPin the script to split if needed.
Model detection is automatic — the script connects to each device and reads the model
string directly from Junos. You never need to specify the model in the targets file.
Unsupported platforms — if the script encounters a model it does not recognize, the
dry-run report provides exact step-by-step instructions to add support: FTP directory to
create, package filename format, and the two code changes needed in the script.
The script uses anonymous FTP (no credentials) to scan available versions.
Default server: 192.168.10.1 (override with --ftp-server).
vsftpd is configured with anon_root=/srv/ftp, so the client-visible path starts at /code/:
/srv/ftp/ <- vsftpd anon_root (chmod 755, not writable — required by vsftpd)
└── code/
├── EX2300/ <- EX2300 and EX2300-C both use this directory
├── EX3400/
├── EX4100/
├── EX4600/
├── EX4650/
├── MX204/
└── SRX300/
Each directory may contain multiple version files. Drop new .tgz files in and the
script detects them automatically on the next run — no configuration changes needed.
Uploading code files: Use your local Linux user account (must be in the ftp group).
The script connects as anonymous and is read-only — it never writes to the FTP server.
The script connects to devices via NETCONF on port 830, not regular SSH on port 22.
Both services must be enabled on every target device:
configure
set system services ssh
set system services netconf ssh
set system services netconf rfc-compliant
commit
If NETCONF is not enabled the script detects it automatically and prints the exact
commands needed to fix it. Add these to your ZTP staging config (access.conf) so
all future devices get NETCONF enabled automatically during ZTP provisioning.
Juniper recommends not skipping more than 3 available releases in a single upgrade.
The script scans the FTP directory, sorts all available versions, and plans the path:
- <=3 available releases behind latest -> direct upgrade in one run.
- More than 3 behind -> stages the 3rd-closest available release, stops, and tells
you to reboot and re-run. The next run automatically plans the next hop.
One hop per run — always. The script never chains hops automatically. You reboot,
verify the device came up healthy on the new version, then re-run for the next hop.
This ensures each upgrade is verified before committing to the next one.
Example — FTP has: 21.4, 23.4 — Device running 18.1:
Run 1: 18.1 -> 21.4 staged, reboot, verify
Run 2: 21.4 -> 23.4 staged, reboot, done
The dry-run validation report shows the full multi-hop plan and verifies every
package file in the chain with a checkmark before you touch any device.
pip install junos-eznc lxml paramiko --break-system-packagesOr use the setup script (recommended — does everything for you):
chmod +x setup.sh
sudo ./setup.sh- Python 3.8+
junos-eznc— Juniper PyEZ (pulls in ncclient automatically)lxml— XML parsing for NETCONF responsesparamiko— SSH for OAM mount (EX2300/EX3400) and MX204 CLI installftplib,concurrent.futures— Python standard library, no install needed
- NETCONF enabled (port 830)
- SSH enabled (port 22)
- Management IP reachable from the Pi on both ports
- Root password required only for EX2300 / EX3400 (OAM filesystem mount)
Run once on your Raspberry Pi or jump host:
chmod +x setup.sh
sudo ./setup.shThe setup script handles everything:
- Installs Python dependencies
- Creates all FTP directories under
/srv/ftp/code/ - Sets correct permissions — anonymous read, local user write
- Adds your user to the
ftpgroup so you can upload code files - Validates vsftpd configuration and flags any issues
- Creates the local
logs/directory
One IP address or hostname per line. Comments (#) are supported inline or at line start.
No model numbers needed — the script auto-detects everything from the device.
192.168.1.10 # access-sw-01
192.168.1.20 # core-sw-01 (VC pair)
192.168.1.21 # core-sw-02
192.168.1.30
192.168.1.40
sudo ./setup.shpython3 juniper_upgrade.py --targets targets.txt --dry-runAlways run dry-run first. It validates:
- NETCONF connectivity to every device (with exact fix commands if it fails)
- Model recognition and platform mapping
- FTP directory accessibility and version detection
- Every package file in the full upgrade hop chain (checkmark = present, X = missing)
- Whether intermediate hop files exist for multi-hop upgrades
- Devices already at latest, newer than repo, or unreachable
python3 juniper_upgrade.py --targets targets.txtUpgrades one device at a time. If something goes wrong it only affects one device.
python3 juniper_upgrade.py --targets targets.txt --parallel-upgradesAll selected devices upgrade at the same time using parallel threads. Output
may interleave during the run but the final report is always clean and ordered.
A warning is shown before the confirm prompt so you know what you are committing to.
Good for large access layer batches where all devices are the same model and you
are confident in the code. Still one hop per device per run.
--targets targets.txt Targets file (default: targets.txt)
--ftp-server 192.168.10.1 FTP server IP (default: 192.168.10.1)
--user netadmin SSH username (prompted if omitted)
--connect-timeout 30 Seconds before giving up on a device (default: 30)
--workers 10 Max parallel threads for discovery and parallel upgrades (default: 10)
--parallel-upgrades Upgrade all selected devices simultaneously
--dry-run Validate only, no installs
1. Read targets.txt (IPs/hostnames, one per line, # comments ignored)
2. Prompt for SSH username, password, root password (root for EX2300/3400 only)
3. Create timestamped run directory: logs/YYYY-MM-DD_HH-MM-SS_{dry-run|upgrade}/
4. Discovery pass — parallel threads, one per device:
├── Connect via PyEZ NETCONF (port 830)
├── Read model string and running version from device
├── Check virtual chassis membership (show virtual-chassis)
├── Scan FTP directory for available versions
└── Plan upgrade path using rule of 3 (one hop per run)
5. [--dry-run] Print full validation report with per-hop file checks, save to logs/, exit
6. [real run] Print discovery summary table
7. Interactive device selection — A (all), N (none), or comma-separated numbers e.g. 1,3
8. Confirm prompt (parallel mode shows extra warning)
9. Upgrade phase — sequential or parallel depending on flag:
├── OAM filesystem mount — EX2300/EX3400 only (requires root password)
├── VC health check — all members must be Present before proceeding
├── PRE-UPGRADE SAFETY (runs before every install):
│ ├── request system configuration rescue save
│ ├── request system snapshot all-members
│ └── request system snapshot slice alternate
├── Roll back any existing pending install
├── Install NEXT HOP ONLY via FTP (no-validate):
│ ├── EX2300/3400/4100/4600/4650/SRX300: PyEZ SW.pkgadd()
│ └── MX204: paramiko CLI -> request vmhost software add <url> no-validate
├── Confirm Pending: version directly from show version on device
└── On failure: request system software rollback (automatic, result shown in report)
10. Final summary report printed to screen and saved to logs/run-dir/summary.txt
| State | Meaning |
|---|---|
| STAGED | Software staged to pending partition. Pending version confirmed from device. |
| FAILED | Install failed. request system software rollback issued automatically. |
| MANUAL_REQUIRED | VC member missing, OAM issue, or other condition requiring human review. |
| ALREADY_CURRENT | Device is already running the latest version in the FTP directory. |
| NEWER_THAN_REPO | Device is running a version newer than anything in the FTP directory. |
| NO_MATCH | Model not recognized. Dry-run shows exact steps to add support. |
| FTP_ERROR | FTP directory unreachable or no matching package files found. |
| CONNECT_ERROR | NETCONF connection failed. Fix commands printed if NETCONF is not enabled. |
Before every install the script automatically runs:
request system configuration rescue save
request system snapshot all-members
request system snapshot slice alternate
These are non-fatal — if a snapshot fails it is logged as a warning and the upgrade
continues. The final report shows whether they completed successfully.
Snapshot output from the device is captured and displayed, e.g.:
NOTICE: Snapshot snap.20260614.095902 created successfully
- VC detection is automatic via
show virtual-chassisRPC. - All VC members must be in
prsnt(Present) state — upgrade aborts with
MANUAL_REQUIREDif any member is absent. - Install command uses
all-membersflag for EX3400, EX4100, EX4600, EX4650 in VC
so all members are staged in one operation. - Snapshots use
all-membersflag on VC devices.
These platforms have limited flash space and require extra steps:
- OAM filesystem mount — the script mounts
/dev/gpt/oam/via root SSH before
installing. Root password is required and prompted at startup. If no root password
is provided and an EX2300/3400 is selected for upgrade, it reports MANUAL_REQUIRED. - Helper packages —
os-packageandpackage-hooksmay be needed on some versions.
These are not handled by this script (see Juniper KB31198 and KB31201).
The MX204 uses a vmhost architecture. The install command is a CLI op command:
request vmhost software add ftp://192.168.10.1/code/MX204/<package>.tgz no-validate
This is not a standard NETCONF RPC so the script drops out of PyEZ and drives the
command via a paramiko interactive shell, streaming output until Junos confirms
completion. Install typically takes 10-20 minutes.
Rollback on failure uses request vmhost software rollback.
Every run creates a timestamped directory:
logs/
├── 2026-06-14_15-42-11_dry-run/
│ ├── run.log <- full terminal output, plain text (no ANSI colour codes)
│ └── summary.txt <- clean summary of all device outcomes
└── 2026-06-14_16-05-33_upgrade/
├── run.log
└── summary.txt
If an install returns any failure (fail, failed, exception, or ok=True with fail text
in the output), rollback is issued automatically and the result shown in the report:
✘ FAILED
Reason : Install of 23.4R2-S8.7 failed: ...
↩ ROLLED BACK : request system software rollback — SUCCESS
-- or --
↩ ROLLBACK FAILED: request system software rollback — MANUAL ACTION REQUIRED
Run manually : request system software rollback
If the script connects to a device with an unrecognized model (e.g. ACX7100, QFX5100),
the dry-run report prints exact instructions to add support:
Status NO MATCH — platform not supported yet
To add support for acx7100-48l:
Step 1 Create FTP directory on the Pi:
sudo mkdir -p /srv/ftp/code/ACX7100
sudo chmod 775 /srv/ftp/code/ACX7100
sudo chown root:ftp /srv/ftp/code/ACX7100
Step 2 Upload the Junos package to:
/srv/ftp/code/ACX7100/junos-{version}.tgz
Note: verify the correct package filename for ACX7100 on juniper.net
Step 3 Add to PLATFORM_MAP in juniper_upgrade.py:
"ACX7100": {
"ftp_dir": "ACX7100",
"pkg_template": "junos-{version}.tgz",
"vc_flag": False,
"oam_mount": False,
"vmhost": False,
},
Step 4 Add to detect_platform() in juniper_upgrade.py:
if re.match(r"acx7100", m):
return "ACX7100"
Drop new .tgz files into the appropriate /srv/ftp/code/<PLATFORM>/ directory.
The script auto-detects all available versions on each run — no changes needed.
# Example: adding a new EX2300 release
cp junos-arm-32-24.2R1.17.tgz /srv/ftp/code/EX2300/The next dry-run will automatically include the new version in the upgrade path planning.
| Platform | Rollback Command |
|---|---|
| EX2300, EX2300-C, EX3400, EX4100 | request system software rollback |
| EX4600, EX4650, SRX300 | request system software rollback |
| MX204 | request vmhost software rollback |
This tool is provided as-is with no warranty. Always dry-run first, test on a single non-critical device, and verify the result before upgrading production infrastructure at scale. Use at your own risk. See the full disclaimer at the top of this document.# junos-upgrader