I have been monitoring several parts of my QNAP NAS from Home Assistant for quite some time, and one thing I still wanted to improve was firmware update monitoring. The goal sounds simple: let Home Assistant tell me when a new firmware version is available for my QNAP TS-253D.
In practice, there is an important complication. QNAP firmware releases do not always change the main QTS version number. For example, my NAS was running:
QTS 5.2.10
Build 20260722
while QNAP was offering:
QTS 5.2.10.3577
Build 20260731
Official
Both releases are therefore called 5.2.10, but they have different build numbers. That means a simple comparison of:
5.2.10 == 5.2.10
would incorrectly tell us that no update exists. The solution described in this article uses QNAP’s own firmware update mechanism to determine the firmware that applies to the NAS, converts that information into JSON, and publishes it to MQTT. Home Assistant then only needs to subscribe to the MQTT topic.
The resulting flow is:
QNAP TS-253D
|
| /sbin/auto_update -d
|
v
Shell script
|
| mosquitto_pub
|
v
MQTT broker
|
v
Home Assistant
No firmware installation is performed by Home Assistant. This solution is monitoring-only, and QTS remains completely responsible for actually downloading and installing firmware updates.
What the final MQTT message looks like
The final payload published by my NAS looks like this:
{
"installed_version": "5.2.10",
"installed_build": "20260722",
"latest_version": "5.2.10",
"latest_build": "20260731",
"latest_number": "3577",
"publication_date": "Tue, 04 Aug 2026 16:40:02 +0800",
"checked_at": "2026-08-16 16:58:20",
"update_available": true
}
Home Assistant can therefore see not only that an update exists, but also which build is installed, which build is available, the QTS revision number, and when the check was last performed.
1. The first attempt: QNAP’s Live Update configuration
I started by checking what QTS stores in /etc/config/uLinux.conf. The installed QTS version can be obtained with:
getcfg System Version -f /etc/config/uLinux.conf
On my TS-253D this returned:
5.2.10
The installed build number can be retrieved with:
getcfg System "Build Number" -f /etc/config/uLinux.conf
Output:
20260722
I also inspected update-related settings:
grep -i "update" /etc/config/uLinux.conf
This returned, among other settings:
Enable Live Update = TRUE
QTS Live Update XML = https://update.qnap.com/QTS_FW.xml
Hero Live Update XML = https://update.qnap.com/QTS_Hero_FW.xml
Latest Check Live Update = 2026/08/16 16:14:41
Update Remind Time = 0
Latest Live Update = 2026/07/29 10:35:46
SMTP_FW_UPDATE = 0
SMS_FW_UPDATE = 0
PUSH_FW_UPDATE = 0
migration_app_update = 2
auto_update = TRUE
update_policy = 0
auto_update_required_qpkg = 1
At first sight, the following looked promising:
getcfg System "Live Update Available" -f /etc/config/uLinux.conf
However, while QTS was visibly offering me a firmware update in the GUI, this value did not indicate an update. I therefore decided not to use this flag.
2. Investigating QNAP’s firmware XML
The configuration showed this URL:
https://update.qnap.com/QTS_FW.xml
I queried it directly:
wget -qO- https://update.qnap.com/QTS_FW.xml | grep -i -A 10 -B 5 "TS-253D"
The TS-253D was correctly found as part of the TS-X53D firmware family:
<productName>HS-453DX,TBS-453DX,TS-251D,TS-253D,TS-451D,TS-451D2,TS-453D,TS-453Dmini,TS-653D</productName>
<modelName>TS-X53D</modelName>
<status>active</status>
<deployment/>
<firmware>
<version>5.0.1</version>
<number>2425</number>
<buildNumber>20230609</buildNumber>
That immediately showed another problem. The generic XML file was reporting:
5.0.1
Build 20230609
while my NAS was already running:
5.2.10
Build 20260722
So the generic QTS_FW.xml file clearly was not the modern live-update feed that QTS 5.2 was actually using.
3. Finding QNAP’s internal firmware-update tools
I searched the NAS for firmware and update-related executables:
find /sbin /usr/sbin /bin /usr/bin /home/httpd/cgi-bin \
-type f \( -iname '*firmware*' -o -iname '*update*' -o -iname '*liveupdate*' \) \
2>/dev/null | head -100
The output contained several interesting files:
/sbin/qddns_update
/sbin/auto_update
/sbin/sas_updater
/sbin/eeupdate64e_e810
/sbin/eeupdate64e
/sbin/live_update
/usr/sbin/ez-ipupdate
/home/httpd/cgi-bin/apps/systemPreferences/functions/firmware.js
/home/httpd/cgi-bin/upload_firmware.cgi
The two most interesting binaries were:
/sbin/live_update
/sbin/auto_update
Running:
/sbin/live_update --help
returned:
usage:
[ check_new_firmware | get_new_firmware ] xml_url [temp dir]
Inspecting strings inside the executable also showed firmware-related functions:
strings /sbin/live_update | grep -Ei "https?://|xml|cgi|firmware|version|build|update" | head -100
Among the output:
Get_System_Version
Get_Build_Number
buildNumber
updateInfoURL
get_live_firmware_version
live firmware version = %s%s
check_new_firmware
get_new_firmware
This confirmed that QTS contains its own firmware-checking logic. However, the real breakthrough came from /sbin/auto_update.
4. Using QNAP’s own firmware selection logic
Run:
/sbin/auto_update -d
This produces diagnostic information about QNAP’s firmware update system. On my TS-253D, the beginning of the output looked like this:
latest = [1] recommended = [1] enable_important = [1]
action_mode = [2]
recommended xml_url = https://update.qnap.com/QTS_FW_5.2.0_recommended.xml
latest xml_url = https://update.qnap.com/QTS_FW_5.2.0.xml
Important Security xml_url = https://update.qnap.com/QTS_FW_5.2.0_security.xml
tmpdir = /mnt/update
sys_model_name = TS-X53D
display_model_name = TS-253D
local_version = 5.2.10
local_build_num = 20260722
product = storage
This was exactly the information I needed. QTS itself knew that:
Physical model: TS-253D
Firmware family: TS-X53D
Installed QTS: 5.2.10
Installed build: 20260722
Further down, the command showed the firmware QNAP currently offered for this NAS:
productName = HS-453DX
ptr_model = TS-253D
model = TS-X53D
status = active
version = 5.2.10
number = 3577
build number = 20260731
version_postfix = Official
severity = 3
pubDate = Tue, 04, Aug 2026 16:40:02 +0800
downloadURL[0] = https://download.qnap.com/Storage/TS-X53D/TS-X53D_20260731-5.2.10.3577.zip
downloadURL[1] = https://eu1.qnap.com/Storage/TS-X53D/TS-X53D_20260731-5.2.10.3577.zip
downloadURL[2] = https://us1.qnap.com/Storage/TS-X53D/TS-X53D_20260731-5.2.10.3577.zip
The critical comparison therefore was:
Installed build: 20260722
Available build: 20260731
Because:
20260731 > 20260722
an update is available. I verified this against the QTS GUI, which displayed:
New firmware updates are available
5.2.10.3577 Build 20260731 Official
Latest update
So /sbin/auto_update -d was producing exactly the same firmware version that the QTS GUI was offering, which made it the ideal data source.
5. Creating the firmware status script
I created a shell script that runs /sbin/auto_update -d, extracts the installed firmware information, extracts the latest available firmware information, compares the build numbers, and creates a JSON response.
The script is stored at:
/share/homes/rutger2/qnap_fw_status.sh
To create the file without using vi, I used a cat heredoc. At this stage, before MQTT was added, the core script looked like this:
cat > /share/homes/rutger2/qnap_fw_status.sh <<'EOF'
#!/bin/sh
OUTPUT=$(/sbin/auto_update -d 2>&1)
LOCAL_VERSION=$(echo "$OUTPUT" | grep '^local_version = ' | head -1 | sed 's/^local_version = //')
LOCAL_BUILD=$(echo "$OUTPUT" | grep '^local_build_num = ' | head -1 | sed 's/^local_build_num = //')
LATEST_VERSION=$(echo "$OUTPUT" | grep '^version = ' | head -1 | sed 's/^version = //')
LATEST_BUILD=$(echo "$OUTPUT" | grep '^build number = ' | head -1 | sed 's/^build number = //')
LATEST_NUMBER=$(echo "$OUTPUT" | grep '^number = ' | head -1 | sed 's/^number = //')
PUBDATE=$(echo "$OUTPUT" | grep '^pubDate = ' | head -1 | sed 's/^pubDate = //')
if [ -n "$LOCAL_BUILD" ] && [ -n "$LATEST_BUILD" ] && [ "$LATEST_BUILD" -gt "$LOCAL_BUILD" ]; then
UPDATE_AVAILABLE=true
else
UPDATE_AVAILABLE=false
fi
printf '{"installed_version":"%s","installed_build":"%s","latest_version":"%s","latest_build":"%s","latest_number":"%s","publication_date":"%s","update_available":%s}\n' \
"$LOCAL_VERSION" \
"$LOCAL_BUILD" \
"$LATEST_VERSION" \
"$LATEST_BUILD" \
"$LATEST_NUMBER" \
"$PUBDATE" \
"$UPDATE_AVAILABLE"
EOF
The file then needs executable permission:
chmod +x /share/homes/rutger2/qnap_fw_status.sh
Testing it:
/share/homes/rutger2/qnap_fw_status.sh
returned:
{"installed_version":"5.2.10","installed_build":"20260722","latest_version":"5.2.10","latest_build":"20260731","latest_number":"3577","publication_date":"Tue, 04 Aug 2026 16:40:02 +0800","update_available":true}
At this point, the firmware detection itself was complete.
6. Why I moved away from Home Assistant SSH polling
Initially I considered having Home Assistant execute the script remotely through the Command Line integration:
- sensor:
name: "QNAP Firmware Update"
unique_id: qnap_firmware_update
command: "ssh -i /config/.ssh/qnap_hass hass@192.168.1.95 /share/homes/rutger2/qnap_fw_status.sh"
scan_interval: 3600
value_template: "{{ value_json.update_available }}"
Running the SSH command manually from the Home Assistant terminal worked perfectly. However, the Command Line integration occasionally returned:
Command failed (with return code 255)
followed by:
Empty reply found when expecting JSON data
Because /sbin/auto_update -d performs an external QNAP firmware check, it can take some time. Rather than having Home Assistant poll the NAS over SSH, I decided to reverse the direction:
QNAP → MQTT → Home Assistant
This turned out to be a much cleaner solution.
7. Installing an MQTT client on QNAP
My QNAP installation did not contain mosquitto_pub. Checking:
which mosquitto_pub
returned nothing. The NAS did have curl:
which curl
Output:
/sbin/curl
and also nc:
which nc
Output:
/usr/local/sbin/nc
Neither is an ideal replacement for a real MQTT client, so I installed Entware.
8. Installing Entware on QNAP
I installed the Entware QPKG through QNAP App Center using the manual installation option. After installation, I verified opkg:
/opt/bin/opkg --version
Output:
opkg version 80503d94e356476250adaf1f669ee955ec26de76 (2025-11-05)
The first attempt to update the package list as my normal QNAP user failed:
/opt/bin/opkg update
Output:
Downloading http://bin.entware.net/x64-k3.2/Packages.gz
*** Failed to download the package list from http://bin.entware.net/x64-k3.2/Packages.gz
Collected errors:
* file_move: Failed to rename /opt/tmp/opkg-.../Packages.gz to /opt/var/opkg-lists/entware: Permission denied.
The problem was simply permissions. Running it through sudo worked:
sudo /opt/bin/opkg update
Output:
Downloading http://bin.entware.net/x64-k3.2/Packages.gz
Updated list of available packages in /opt/var/opkg-lists/entware
9. Installing mosquitto_pub
I then installed the Mosquitto client package:
sudo /opt/bin/opkg install mosquitto-client
After installation:
/opt/bin/mosquitto_pub --help
showed:
mosquitto_pub is a simple mqtt client that will publish a message on a single topic and exit.
mosquitto_pub version 2.0.22 running on libmosquitto 2.0.22.
MQTT publishing was now available directly on the NAS.
10. Testing MQTT from QNAP
Before modifying the firmware script, I tested publishing a simple retained message. My MQTT topic is:
qnap/notifications/fw_update
The test command was:
/opt/bin/mosquitto_pub \
-h MQTT_BROKER_IP \
-p 1883 \
-u qnap \
-P 'MQTT_PASSWORD' \
-t "qnap/notifications/fw_update" \
-m '{"test":true}' \
-r
Replace:
MQTT_BROKER_IP
MQTT_PASSWORD
with your own MQTT broker address and credentials. The -r option is important because it makes the message retained, which means Home Assistant immediately receives the last known firmware state after a restart even if the QNAP does not perform another check immediately.
The test message appeared successfully:
{
"test": true
}
With MQTT communication proven, I could integrate it into the firmware script.
11. Final QNAP firmware-to-MQTT script
This is the complete script I now use. Do not publish your MQTT password publicly; replace the placeholders with your own values.
cat > /share/homes/rutger2/qnap_fw_status.sh <<'EOF'
#!/bin/sh
MQTT_HOST="MQTT_BROKER_IP"
MQTT_PORT="1883"
MQTT_USER="qnap"
MQTT_PASSWORD="YOUR_MQTT_PASSWORD"
MQTT_TOPIC="qnap/notifications/fw_update"
OUTPUT=$(/sbin/auto_update -d 2>&1)
LOCAL_VERSION=$(echo "$OUTPUT" | grep '^local_version = ' | head -1 | sed 's/^local_version = //')
LOCAL_BUILD=$(echo "$OUTPUT" | grep '^local_build_num = ' | head -1 | sed 's/^local_build_num = //')
LATEST_VERSION=$(echo "$OUTPUT" | grep '^version = ' | head -1 | sed 's/^version = //')
LATEST_BUILD=$(echo "$OUTPUT" | grep '^build number = ' | head -1 | sed 's/^build number = //')
LATEST_NUMBER=$(echo "$OUTPUT" | grep '^number = ' | head -1 | sed 's/^number = //')
PUBDATE=$(echo "$OUTPUT" | grep '^pubDate = ' | head -1 | sed 's/^pubDate = //')
CHECKED_AT=$(date "+%Y-%m-%d %H:%M:%S")
if [ -n "$LOCAL_BUILD" ] && [ -n "$LATEST_BUILD" ] && [ "$LATEST_BUILD" -gt "$LOCAL_BUILD" ]; then
UPDATE_AVAILABLE=true
else
UPDATE_AVAILABLE=false
fi
JSON=$(printf '{"installed_version":"%s","installed_build":"%s","latest_version":"%s","latest_build":"%s","latest_number":"%s","publication_date":"%s","checked_at":"%s","update_available":%s}' \
"$LOCAL_VERSION" \
"$LOCAL_BUILD" \
"$LATEST_VERSION" \
"$LATEST_BUILD" \
"$LATEST_NUMBER" \
"$PUBDATE" \
"$CHECKED_AT" \
"$UPDATE_AVAILABLE")
echo "$JSON"
/opt/bin/mosquitto_pub \
-h "$MQTT_HOST" \
-p "$MQTT_PORT" \
-u "$MQTT_USER" \
-P "$MQTT_PASSWORD" \
-t "$MQTT_TOPIC" \
-m "$JSON" \
-r
EOF
Make it executable:
chmod +x /share/homes/rutger2/qnap_fw_status.sh
Then manually test it:
/share/homes/rutger2/qnap_fw_status.sh
My output was:
{"installed_version":"5.2.10","installed_build":"20260722","latest_version":"5.2.10","latest_build":"20260731","latest_number":"3577","publication_date":"Tue, 04 Aug 2026 16:40:02 +0800","checked_at":"2026-08-16 16:58:20","update_available":true}
At exactly the same time, the retained MQTT message was updated.
12. Automatically checking every six hours
The firmware check does not need to run every few minutes. Firmware releases are infrequent, and /sbin/auto_update -d talks to QNAP’s update infrastructure, so I decided to run the script once every six hours.
QNAP stores its persistent cron configuration in:
/etc/config/crontab
I added the following job:
sudo sh -c 'echo "15 */6 * * * /share/homes/rutger2/qnap_fw_status.sh >/dev/null 2>&1" >> /etc/config/crontab'
This runs the firmware check at:
00:15
06:15
12:15
18:15
Reload the QNAP crontab:
sudo crontab /etc/config/crontab
Then restart the cron daemon:
sudo /etc/init.d/crond.sh restart
Verify the entry:
grep qnap_fw_status /etc/config/crontab
Output:
15 */6 * * * /share/homes/rutger2/qnap_fw_status.sh >/dev/null 2>&1
The QNAP now checks its own firmware status every six hours and publishes the result to MQTT.
13. Creating the Home Assistant MQTT sensor
The MQTT topic used by the script is:
qnap/notifications/fw_update
A simple Home Assistant MQTT sensor can subscribe to it. For example:
- name: "MQTT QNAP Firmware Update"
unique_id: mqtt_qnap_firmware_update
state_topic: "qnap/notifications/fw_update"
value_template: >
{% if value_json.update_available %}
Update available
{% else %}
Up to date
{% endif %}
json_attributes_topic: "qnap/notifications/fw_update"
icon: mdi:chip
The main sensor state will therefore be either:
Update available
or:
Up to date
Because the same MQTT payload is also used as json_attributes_topic, Home Assistant receives all additional information as attributes. For example:
installed_version: "5.2.10"
installed_build: "20260722"
latest_version: "5.2.10"
latest_build: "20260731"
latest_number: "3577"
publication_date: "Tue, 04 Aug 2026 16:40:02 +0800"
checked_at: "2026-08-16 16:58:20"
update_available: true
This provides much more useful information than a simple binary update flag.
14. Why comparing the build number matters
One of the most important lessons from this project is that the QTS version number alone is not enough. In this case:
Installed version: 5.2.10
Latest version: 5.2.10
A normal string comparison would suggest that the system is already current. However, the builds were:
Installed build: 20260722
Latest build: 20260731
and QTS itself was offering:
5.2.10.3577 Build 20260731 Official
The update detection therefore intentionally compares:
"$LATEST_BUILD" -gt "$LOCAL_BUILD"
rather than only comparing the QTS version strings.
15. Why use /sbin/auto_update -d instead of scraping QNAP’s website?
There is another advantage to this method. A QNAP model does not necessarily have its own independent firmware package; my TS-253D belongs to the firmware family:
TS-X53D
QNAP also applies deployment logic when deciding which firmware should be offered to a particular NAS. The diagnostic output showed, for example:
sys_model_name = TS-X53D
display_model_name = TS-253D
fw_deployment = 0000100000000000
By using:
/sbin/auto_update -d
I let QTS perform all of this model, deployment, and firmware-selection logic itself. The script does not need to know which XML file applies, which firmware family the NAS belongs to, or which staged deployment applies.
What firmware do you currently consider applicable to this NAS?
That is effectively the question the script asks QTS, and it makes this approach much more robust than scraping QNAP’s Download Center or manually parsing the generic firmware XML.
16. Security considerations
There are a few things worth keeping in mind. The MQTT password is currently stored inside the shell script:
MQTT_PASSWORD="YOUR_MQTT_PASSWORD"
That means anyone with sufficient access to the NAS and that file can read the credentials. For a home network this may be acceptable, particularly if the MQTT account has very limited permissions, but I recommend creating a dedicated MQTT user for the NAS.
For example, the qnap MQTT user only needs permission to publish to:
qnap/notifications/#
It does not need access to every MQTT topic. Also make sure the script itself is not exposed through a web server.
This solution only checks firmware availability. It deliberately does not invoke any firmware installation or download command. I specifically avoid calling:
get_new_firmware
or any firmware installation action. Firmware installation remains a manual operation through QTS.
17. Troubleshooting
/opt/bin/opkg update returns Permission denied
Run it through sudo:
sudo /opt/bin/opkg update
Do not solve this by recursively changing permissions on /opt.
mosquitto_pub is not found
Check:
which mosquitto_pub
If Entware installed it, the executable should normally be available at:
/opt/bin/mosquitto_pub
Use the full path in the firmware script so cron does not depend on your interactive shell’s PATH.
The script works manually but not through cron
Always use absolute paths in cron scripts. For example:
/sbin/auto_update
/opt/bin/mosquitto_pub
instead of:
auto_update
mosquitto_pub
The cron environment is much smaller than your normal interactive shell.
MQTT state remains old
Because the message is retained, the last successful firmware check remains visible. Look at:
checked_at
to determine when the NAS last successfully refreshed the information.
QTS version is identical but Home Assistant says an update exists
That can be completely correct. Check:
installed_build
latest_build
The build number is what the script uses to determine whether the firmware is newer.
Final result
What started as a simple firmware-version comparison ended up revealing quite a bit about how QNAP’s own update system works. The key discovery was:
/sbin/auto_update -d
This exposes the firmware information QTS itself uses, including the physical NAS model, firmware family, installed version, installed build, and the firmware currently offered by QNAP.
The complete solution now works autonomously:
Every six hours
QNAP TS-253D
↓
/sbin/auto_update -d
↓
Compare installed and available build
↓
Create JSON
↓
mosquitto_pub
↓
qnap/notifications/fw_update
↓
Home Assistant
In my current situation Home Assistant reports:
Installed:
QTS 5.2.10
Build 20260722
Available:
QTS 5.2.10.3577
Build 20260731
Status:
Update available
The biggest advantage is that Home Assistant does not need to understand QNAP’s firmware catalogue at all. QTS determines which firmware is appropriate for the NAS, while Home Assistant simply receives the result over MQTT. Because the firmware update itself remains completely under QTS control, the automation only adds visibility without adding any risk of an unattended NAS firmware installation.
“`
Related Posts
May 30, 2026
Monitor QNAP Updates in Home Assistant
October 20, 2025


