Storing Reolink Camera Recordings on a Tapo H500 Smart HomeBase (FTP)
Quick summary: The TP-Link Tapo H500 Smart HomeBase has a USB port, and with a drive attached it can double as network storage for your cameras — no separate NAS or always-on PC needed. That makes it an appealing place to park recordings from a Reolink camera (an RLC-810A, RLC-520A, E1 Pro, or similar) via Reolink's built-in FTP upload. The catch: pointing a Reolink camera at the H500's FTP share tends to fail with a useless 19Error: Unknown error popup. That single error turned out to hide three separate problems — a remote directory that didn't exist, active-mode data connections timing out on the Tapo's embedded FTP server, and a "TLS Support" toggle advertising a feature (AUTH TLS) the server doesn't implement. This post gives the working configuration for storing Reolink footage on a Tapo H500, and walks through the diagnostic scripts used to get there.
TL;DR — Settings to Store Reolink Recordings on a Tapo H500
To save recordings from a Reolink camera (tested with FTP-capable models like the RLC-810A, RLC-520A, and E1 Pro) onto a TP-Link Tapo H500 Smart HomeBase with a USB drive attached, use these FTP settings in the Reolink app:
| Reolink FTP setting | Value |
|---|---|
| Server Address | Your Tapo H500's LAN IP (e.g. 192.168.1.115) |
| Server Port | 21 |
| Anonymous FTP | Off |
| Username | The FTP account username you set in the Tapo app's "Access Authentication" screen |
| Password | The matching password |
| Transport Mode | Passive (PASV) if selectable — do not rely on AUTO |
| Disable Plain Unencrypted FTP | Off — the Tapo H500's FTP server does not actually support FTPS/AUTH TLS despite the app's "Enable TLS Support" toggle |
| Remote Directory | A subfolder inside the drive's exposed folder (e.g. /G/reolink), not the FTP root — see below for why |
The rest of this post explains how we found that, in case your symptoms differ and you need to adapt the method rather than just copy the answer.
Why Store Reolink Recordings on a Tapo H500?
The Tapo H500 Smart HomeBase has a USB port, and with a drive plugged in it acts as network storage for anything on your LAN. It exposes that storage over FTP, which happens to be exactly the upload protocol Reolink cameras support natively. So instead of buying microSD cards for each camera, running a dedicated NVR, or leaving a PC on 24/7, you can point your Reolink cameras at the H500 and have them write footage straight to the shared USB drive. Reolink's FTP upload works the same way across most of their lineup — wired PoE models like the RLC-810A and RLC-520A, and Wi-Fi models like the E1 Pro all expose the same FTP settings screen.
That's the appeal. Getting it working is where the 19Error shows up.
The Problem: Reolink's FTP Test Fails with "19Error"
Setting up FTP on the Tapo side is straightforward: turn on FTP (Local) in the hub's USB Settings, note the ftp://<ip>:21 address it shows, and set a username/password under Access Authentication.
Pointing the Reolink camera at that address, however, produced a dead-end error every time:
Searching for this exact error turned up nothing useful — Reolink's own support docs only document FTP failures in the 450–457 range (standard FTP reply codes), and 19Error isn't one of them. That absence of documentation is itself a useful signal: it means the error is a generic catch-all the Reolink firmware shows whenever something it didn't expect happens, rather than a specific, meaningful code. Debugging it required treating the camera as a black box and testing the FTP server directly instead.
Methodology: Don't Trust the Camera's Error Message, Talk to the Server Yourself
The core idea: a Reolink camera's FTP client is opaque — it either works or it shows an unhelpful generic error. A real FTP client, or a small script using it, shows the actual server response at every step. So instead of guessing at camera settings, the approach was:
- Confirm basic reachability first, independent of any FTP semantics — can we even open a TCP connection to port 21 and see the server's banner?
- Test the connection modes a camera is likely to use, one variable at a time: plain FTP vs. explicit FTPS (
AUTH TLS), and active (PORT) vs. passive (PASV) data connections. That's four combinations, plus the raw banner check. - Read the raw protocol responses, not just "success/fail" — an FTP server that rejects a directory change says so explicitly (
550 /reolink: No such file or directory), and one that doesn't understand a command says so too (500 AUTH not understood). Those two-line responses immediately tell you what's wrong, which the camera's UI never surfaces. - Fix one variable, re-test, confirm — rather than changing several settings on the camera at once and hoping.
This is a general-purpose approach for any "smart" device that talks to a server over a standard protocol but only reports success/failure without detail: bypass the device, script the protocol directly, and read what the server actually says.
Step 1 — Diagnostic script (all combinations)
This script checks the raw TCP banner, then tries plain FTP and explicit FTPS, each in both active and passive mode, logging every server response:
#!/usr/bin/env python3
"""
Reolink -> Tapo H500 FTP diagnostic script.
Mimics the connection modes a Reolink camera commonly uses (plain FTP,
explicit FTPS/AUTH TLS, active vs passive data mode) against the Tapo
H500's FTP share, and prints the raw server responses at each step so
we can see exactly where/why it fails instead of Reolink's generic
"19Error / Unknown error" popup.
Run this from a machine on the SAME LAN as the Tapo hub. Standard
library only -- no pip install needed.
"""
import ftplib
import io
import socket
HOST = "192.168.1.115"
PORT = 21
USER = "admin"
PASSWORD = "REDACTED"
REMOTE_DIR = "/reolink" # set to "" to skip the directory test
TIMEOUT = 8
def banner(title):
print("\n" + "=" * 60)
print(title)
print("=" * 60)
def check_port_open():
banner(f"Raw TCP check: {HOST}:{PORT}")
try:
with socket.create_connection((HOST, PORT), timeout=TIMEOUT) as s:
data = s.recv(200)
print("Connected. Server banner:", data.decode(errors="replace").strip())
except Exception as e:
print("Raw TCP connect failed:", repr(e))
def _common_checks(ftp):
try:
print("PWD:", ftp.pwd())
except Exception as e:
print("PWD failed:", e)
if REMOTE_DIR:
try:
ftp.cwd(REMOTE_DIR)
print(f"CWD {REMOTE_DIR}: OK")
except Exception as e:
print(f"CWD {REMOTE_DIR} failed:", e)
try:
print("Directory listing:")
ftp.retrlines("LIST")
except Exception as e:
print("LIST failed:", e)
def test_plain_ftp(passive):
mode = "PASV" if passive else "PORT/active"
banner(f"Plain FTP, {mode} mode")
try:
ftp = ftplib.FTP(timeout=TIMEOUT)
ftp.set_pasv(passive)
print(ftp.connect(HOST, PORT))
print(ftp.login(USER, PASSWORD))
_common_checks(ftp)
try:
bio = io.BytesIO(b"reolink ftp diag test\n")
ftp.storbinary("STOR ftp_diag_test.txt", bio)
print("Upload test: OK")
ftp.delete("ftp_diag_test.txt")
print("Cleanup: deleted test file")
except Exception as e:
print("Upload test failed:", e)
ftp.quit()
except Exception as e:
print("CONNECT/LOGIN FAILED:", repr(e))
def test_explicit_ftps(passive):
mode = "PASV" if passive else "PORT/active"
banner(f"Explicit FTPS (AUTH TLS), {mode} mode")
try:
ftp = ftplib.FTP_TLS(timeout=TIMEOUT)
ftp.set_pasv(passive)
print(ftp.connect(HOST, PORT))
print("AUTH TLS response:", ftp.auth())
print(ftp.login(USER, PASSWORD))
ftp.prot_p()
_common_checks(ftp)
ftp.quit()
except Exception as e:
print("CONNECT/AUTH/LOGIN FAILED:", repr(e))
if __name__ == "__main__":
check_port_open()
test_plain_ftp(passive=True)
test_plain_ftp(passive=False)
test_explicit_ftps(passive=True)
test_explicit_ftps(passive=False)
banner("Done")
print("Compare which combination succeeded, and how each failure differs.")
The results
Running that against the Tapo H500 produced this (credentials redacted):
============================================================
Raw TCP check: 192.168.1.115:21
============================================================
Connected. Server banner: 220 Welcome to Tapo H500 FTP service
============================================================
Plain FTP, PASV mode
============================================================
220 Welcome to Tapo H500 FTP service
230 User admin logged in
PWD: /
CWD /reolink failed: 550 /reolink: No such file or directory
Directory listing:
drwxrwxrwx 47 0 root 32768 Jan 1 1970 G
Upload test failed: 550 ftp_diag_test.txt: Operation not permitted
============================================================
Plain FTP, PORT/active mode
============================================================
220 Welcome to Tapo H500 FTP service
230 User admin logged in
PWD: /
CWD /reolink failed: 550 /reolink: No such file or directory
Directory listing:
LIST failed: timed out
Upload test failed: cannot read from timed out object
CONNECT/LOGIN FAILED: OSError('cannot read from timed out object')
============================================================
Explicit FTPS (AUTH TLS), PASV mode
============================================================
220 Welcome to Tapo H500 FTP service
CONNECT/AUTH/LOGIN FAILED: error_perm('500 AUTH not understood')
============================================================
Explicit FTPS (AUTH TLS), PORT/active mode
============================================================
220 Welcome to Tapo H500 FTP service
CONNECT/AUTH/LOGIN FAILED: error_perm('500 AUTH not understood')
============================================================
Done
============================================================
Reading the results — three distinct bugs, not one
This one script run diagnosed everything at once:
- Login itself was never the problem.
230 User admin logged insucceeds in every mode. If your credentials were wrong, you'd expect a530response here instead — worth confirming that's not your issue before going further. - The FTP root (
/) isn't where you're supposed to write files. Listing/shows a single folder,G— that's the actual USB drive, exposed as a subfolder rather than being the FTP root itself. Trying toSTORa file directly in/returns550 ... Operation not permitted. And critically,/reolinkdoesn't exist yet anywhere — you can'tcwdinto a directory that was never created. - Active (PORT) mode doesn't work at all on this server — the data connection just times out. Passive (PASV) mode works fine for listing.
- TLS is not implemented, despite the "Enable TLS Support" toggle existing in the Tapo app's FTP settings screen. The server's blunt
500 AUTH not understoodin response to the standardAUTH TLScommand confirms the toggle does not do what its label implies — or at least doesn't enable it on port 21 for explicit FTPS.
Any one of these, on its own, would be enough to make a Reolink camera's connection attempt fail and show its generic 19Error. The only way to see that there were three independent issues was reading the actual protocol-level responses instead of relying on the camera's summary judgment.
Step 2 — Confirming the fix (create the right directory, verify a real upload)
With the root cause identified, the second script creates the missing directory in the correct location and does a full write → list → cleanup round-trip to prove it end-to-end, using only the combination that's actually supported (plain FTP + PASV):
#!/usr/bin/env python3
"""
Reolink -> Tapo H500 FTP diagnostic script (v2).
Findings from the first run:
- Login works fine over plain FTP.
- PASV (passive) mode works; PORT (active) mode times out entirely.
- AUTH TLS is NOT implemented server-side ("500 AUTH not understood"),
despite the "Enable TLS Support" toggle in the Tapo app.
- The FTP root "/" only contains one folder, "G" (the actual USB
drive). Root itself refuses writes -- files must go inside "G".
This version creates a "reolink" folder inside "G" (if missing) and
does a full write/list/delete round-trip there, using plain FTP +
PASV only (the combination that actually works).
"""
import ftplib
import io
import socket
HOST = "192.168.1.115"
PORT = 21
USER = "admin"
PASSWORD = "REDACTED"
BASE_DIR = "/G"
TARGET_DIR = "/G/reolink"
TIMEOUT = 8
def banner(title):
print("\n" + "=" * 60)
print(title)
print("=" * 60)
def check_port_open():
banner(f"Raw TCP check: {HOST}:{PORT}")
try:
with socket.create_connection((HOST, PORT), timeout=TIMEOUT) as s:
data = s.recv(200)
print("Connected. Server banner:", data.decode(errors="replace").strip())
except Exception as e:
print("Raw TCP connect failed:", repr(e))
def main():
check_port_open()
banner("Plain FTP, PASV mode -- create + verify /G/reolink")
try:
ftp = ftplib.FTP(timeout=TIMEOUT)
ftp.set_pasv(True)
print(ftp.connect(HOST, PORT))
print(ftp.login(USER, PASSWORD))
print("PWD:", ftp.pwd())
try:
ftp.cwd(BASE_DIR)
print(f"CWD {BASE_DIR}: OK")
except Exception as e:
print(f"CWD {BASE_DIR} failed:", e)
return
print(f"Listing of {BASE_DIR}:")
ftp.retrlines("LIST")
try:
ftp.mkd("reolink")
print("Created 'reolink' folder under", BASE_DIR)
except ftplib.error_perm as e:
print("MKD response (may already exist):", e)
try:
ftp.cwd(TARGET_DIR)
print(f"CWD {TARGET_DIR}: OK")
except Exception as e:
print(f"CWD {TARGET_DIR} failed:", e)
return
try:
bio = io.BytesIO(b"reolink ftp diag test\n")
ftp.storbinary("STOR ftp_diag_test.txt", bio)
print("Upload into", TARGET_DIR, ": OK")
except Exception as e:
print("Upload failed:", e)
print(f"Listing of {TARGET_DIR}:")
ftp.retrlines("LIST")
try:
ftp.delete("ftp_diag_test.txt")
print("Cleanup: deleted test file")
except Exception as e:
print("Cleanup failed:", e)
ftp.quit()
except Exception as e:
print("CONNECT/LOGIN FAILED:", repr(e))
banner("Done")
print(f"If the upload above succeeded, set Reolink's Remote Directory to: {TARGET_DIR}")
if __name__ == "__main__":
main()
With that folder created and the upload round-trip confirmed, the Reolink camera was pointed at /G/reolink as its Remote Directory, Transport Mode set to Passive, and "Disable Plain Unencrypted FTP" left off. The next FTP test on the camera succeeded, and the camera created its own date-stamped subfolder shortly after, confirmed with a plain curl listing:
$ curl --user admin:PASSWORD ftp://192.168.1.115/G/reolink/
drwxrwxrwx 3 0 root 32768 Sep 13 16:28 2026
That 2026 folder wasn't created by either diagnostic script above — it appeared on its own, timestamped minutes after the camera's FTP settings were saved, which is strong evidence the camera connected and started writing on its own.
Security note: since this testing confirmed the Tapo H500's FTP server doesn't support TLS/FTPS on port 21, credentials and footage travel over plain, unencrypted FTP. That's a reasonable tradeoff on a trusted local network, but this share should not be port-forwarded or otherwise exposed to the internet.
Bonus: Browsing and Downloading What's on the Share
Once footage is uploading correctly, here are a few ways to get at it:
One-off browsing/downloads with curl (works everywhere, no extra install):
curl --user admin:PASSWORD ftp://192.168.1.115/G/reolink/ # list contents
curl --user admin:PASSWORD -O ftp://192.168.1.115/G/reolink/somefile.mp4 # download one file
Interactive session with the classic ftp command (if present on your system):
ftp 192.168.1.115
Name: admin
Password: ********
ftp> passive
ftp> cd /G/reolink
ftp> ls
ftp> mget *
ftp> quit
The passive command matters — remember active mode doesn't work on this server at all.
No terminal at all: on a Mac, Finder → Go → Connect to Server (⌘K) → ftp://admin:[email protected]/G/reolink mounts the folder like a network drive.
Recursive/incremental download script, for pulling everything down (and only re-downloading changed files on subsequent runs):
#!/usr/bin/env python3
"""
Recursively download everything under a folder on the Tapo H500 FTP
share, using plain FTP + PASV mode (the only combination confirmed to
work with this server).
Safe to re-run: it skips files that already exist locally with the
same size as the remote copy, so it works as an incremental sync too.
"""
import ftplib
import os
HOST = "192.168.1.115"
PORT = 21
USER = "admin"
PASSWORD = "REDACTED"
REMOTE_DIR = "/G/reolink" # folder to download -- use "/G" for the whole drive
LOCAL_DIR = "./reolink_downloads" # where to save files locally
TIMEOUT = 15
def parse_list_line(line):
"""Parse a unix-style FTP LIST line -> (is_dir, name), or None if not a real entry."""
parts = line.split(maxsplit=8)
if len(parts) < 9:
return None
perms, name = parts[0], parts[8]
if name in (".", ".."):
return None
return (perms.startswith("d"), name)
def download_dir(ftp, remote_path, local_path):
os.makedirs(local_path, exist_ok=True)
ftp.cwd(remote_path)
lines = []
ftp.retrlines("LIST", lines.append)
for line in lines:
parsed = parse_list_line(line)
if not parsed:
continue
is_dir, name = parsed
remote_item = f"{remote_path.rstrip('/')}/{name}"
local_item = os.path.join(local_path, name)
if is_dir:
print(f"[dir] {remote_item}/")
download_dir(ftp, remote_item, local_item)
ftp.cwd(remote_path) # back out after recursing into subfolder
else:
try:
remote_size = ftp.size(name)
except Exception:
remote_size = None
if (
remote_size is not None
and os.path.exists(local_item)
and os.path.getsize(local_item) == remote_size
):
print(f"[skip] {remote_item} (already downloaded)")
continue
print(f"[get] {remote_item} -> {local_item}")
with open(local_item, "wb") as f:
ftp.retrbinary(f"RETR {name}", f.write)
def main():
ftp = ftplib.FTP(timeout=TIMEOUT)
ftp.set_pasv(True)
ftp.connect(HOST, PORT)
ftp.login(USER, PASSWORD)
print(f"Connected. Downloading {REMOTE_DIR} -> {LOCAL_DIR}")
download_dir(ftp, REMOTE_DIR, LOCAL_DIR)
ftp.quit()
print("Done.")
if __name__ == "__main__":
main()
FAQ
Can I use a Tapo H500 as recording storage for my Reolink cameras?
Yes. Attach a USB drive to the H500, turn on FTP (Local), and point each Reolink camera's FTP upload at the hub using the settings in the TL;DR above. Any Reolink camera with FTP upload support works — this was validated against wired models (RLC-810A, RLC-520A) and a Wi-Fi model (E1 Pro), and the FTP settings screen is the same across the range. Multiple cameras can share one H500; give each its own subfolder under the drive (e.g. /G/reolink/front-door, /G/reolink/driveway) to keep footage separate.
What does Reolink's FTP "19Error / Unknown error" actually mean?
It's not a documented, specific error code — Reolink's own support articles only cover FTP test failures in the 450–457 range. 19Error appears to be a generic fallback the app shows when the FTP exchange fails in a way its UI doesn't have a specific message for. Diagnosing it requires testing the FTP server directly (see the Methodology section above) rather than relying on the app's error text.
Does the Tapo H500 support FTPS (encrypted FTP)?
Based on this testing, no — despite an "Enable TLS Support" toggle existing in the Tapo app's FTP settings, the server responds 500 AUTH not understood to a standard explicit-FTPS AUTH TLS command on port 21. Leave "Disable Plain Unencrypted FTP" off on any camera or client connecting to it.
Why does my Reolink camera time out instead of erroring cleanly? Active-mode (PORT) FTP requires the server to open a new connection back to the client for each data transfer (directory listings, file uploads). Many small embedded FTP servers, including the Tapo H500's, only support passive (PASV) mode reliably. Force passive mode wherever your camera or client allows it.
Why can't I upload files to the FTP server's root directory?
On the Tapo H500, / is a virtual FTP root that only contains the exposed USB drive as a subfolder (shown as /G in this case) — it isn't itself writable. Any directory you configure a camera to upload into needs to exist somewhere under that drive folder, not at the FTP root.
How do I know which subfolder represents my USB drive?
Connect with any FTP client (curl, Finder, or the ftp command) and list the root directory — the drive typically shows up as a single folder with a short, letter-like name (in this case, G).
Key Takeaways
- A vague error message from a consumer device (
19Error: Unknown error) is a signal to stop guessing at settings and start talking to the underlying protocol directly. - A short diagnostic script that tries every likely combination (plain vs. encrypted, active vs. passive) and prints raw server responses will usually surface multiple stacked issues at once, not just one.
- Embedded/consumer FTP servers frequently support only a subset of the protocol — don't assume active mode, FTPS, or a writable root just because the UI offers a toggle for it.
- Once the server's real behavior is understood, fixing the actual device (camera) configuration takes one step, not a dozen rounds of trial and error.
Devices used: TP-Link Tapo H500 Smart HomeBase (USB storage / local FTP share), Reolink IP cameras (RLC-810A, RLC-520A, E1 Pro). Tested from macOS using Python 3's built-in ftplib.