System designHow-to

Authenticated IP camera snapshot dies at session timeout: restream it

When the IP camera login session expires, the tile dies. Fetch each snapshot with authenticated requests and serve it locally, safe from the session timeout.

LumaSpec Vision11 min read Share on LinkedIn

On this page 9 sections
  1. 01Why the tile dies at session timeout
  2. 02Pick the fix for your camera
  3. 03What you need
  4. 04Set it up in six steps
    1. The fetcher client
  5. 05The numbers: what a 1080p tile every 2 s costs
  6. 06Pitfalls that break the restream later
  7. 07When a snapshot restream is the wrong tool
  8. 08Unattended-run checklist
  9. 09Questions

The camera tile on the HMI runs all week, then Monday morning it shows a broken image: the IP camera's web login session expired over the weekend and nothing logged back in. You can fix that permanently in about half an hour by taking the session out of the path entirely. This guide shows how to identify which authentication style your camera uses, move the login into a small client that authenticates every snapshot request, and hand the HMI a locally served JPEG or MJPEG stream no timeout can kill. The load is modest — a 1080p still every 2 s costs about 0.8 Mbit/s — and the tile outlives weekends unattended. The examples below use 192.168.10.21 for the camera and 192.168.10.50 for the fetcher; substitute your own addresses.

Why the tile dies at session timeout

Cameras authenticate image requests in one of two ways, and only one of them expires. Challenge-style cameras accept an Authorization header on every request, HTTP Basic or Digest, and keep no state: there is nothing to time out. Form-style cameras want a login POST from the web UI, then hand out a session cookie governed by two limits — an idle timeout usually set in minutes and an absolute lifetime that can stretch toward a day. A tile that polls all day never idles, so it is the absolute lifetime that lands by morning, which is exactly the fine-at-shift-end, dead-by-morning signature. A camera restart drops those sessions as well.

An HMI tile never logs in; it only displays whatever its image URL returns at the refresh rate you set — the mechanics of that choice live in the guide to a live camera image in an HMI runtime. So the one session, minted once, is never renewed by the tile. Browsers add a second trap: current releases strip userid:password@host from embedded image requests, so credentials pasted into the tile's URL never reach the camera. The request leaves bare, the camera answers 401 or serves its login page, and the tile breaks.

A third trap presses on the same tile: many cameras cap simultaneous sessions per account, so a stuck client or a second viewer can lock the account even mid-shift. If your snapshot stops loading partway through the day and later recovers, that is the connection-limit variant — the guide to an IP camera snapshot that stops loading covers refresh and connection limits.

Pick the fix for your camera

Send one bare request to the snapshot URL and read what comes back. The reply decides the fix:

Camera's reply to a bare image request Authentication style What works
401 with a WWW-Authenticate header naming Basic or Digest Challenge auth Credentials attached to every pull; no session ever exists
200 or 302 carrying an HTML login page Form login plus cookie One login POST, a cookie jar, and a re-login whenever a non-JPEG reply arrives
ONVIF is supported Standard media service GetSnapshotUri returns the still URL; pull it with digest credentials per request

Whatever the row, the shape of the fix is identical: the login moves out of the tile and into a small client running next to the HMI, and the tile points at that client from then on. Per-request authentication is the target because it leaves nothing to expire — the timeout stops mattering instead of getting stretched.

What you need

  • The camera's real snapshot URL. Step 1 finds it; the guide to grabbing images from an IP camera over an HTTP snapshot URL goes deeper.
  • A dedicated read-only account on the camera or its recorder, used by the fetcher and nobody else. Never the installer's admin account.
  • An always-on host with one network port on the camera side and one on the automation side. A vision controller in the panel is the clean answer; where the panel has none, a fanless industrial mini PC does the same job.
  • Half an hour and a browser to watch the tile.

Set it up in six steps

  1. Find the real snapshot URL. Log into the camera in a browser, open the live view, press F12 to open the network tab and reload. The request that returns image/jpeg is the snapshot; copy its full path. Some cameras expose a plain shape such as /media/cam0/still.jpg?res=max, others hide it inside the live-view page.

  2. Ask the camera how it authenticates. Run curl -sD - -o /dev/null "http://192.168.10.21/media/cam0/still.jpg?res=max". A 401 with a WWW-Authenticate header naming Basic or Digest means per-request credentials will work. A 200 or 302 carrying HTML means a form login, and you need the re-login variant described with the fetcher. If the camera speaks ONVIF, call GetSnapshotUri in its media service for the still URL — the image pull itself still carries digest credentials.

  3. Prove the credentials work per request. Run curl -u snap-ro --digest -sD - -o frame.jpg "http://192.168.10.21/media/cam0/still.jpg?res=max"; curl prompts for the secret, so it stays off shell history. Expect HTTP 200 and a frame.jpg whose first two bytes are FF D8 — xxd frame.jpg | head -1 prints them. For a Basic-auth camera, drop the --digest flag.

  4. Run the fetcher client in the next section, edited with your values from steps 1-3.

  5. Point the tile at the fetcher. Set the HMI image widget's URL to the fetcher, for example http://192.168.10.50:8081/, and leave the refresh at 2 s or slower. If your runtime prefers a plain still, have the fetcher also write frame.jpg to a web-served folder and refresh it in an iframe snapshot. A few cameras offer a native MJPEG CGI path, and if your runtime can attach an Authorization header to an image request you can skip the fetcher — the guide to finding the MJPEG CGI URL when RTSP fails shows where those paths hide.

  6. Leave it running over a weekend and read the log. At the 2 s setting a full day is about 43,200 pulls, zero logins after startup and zero 401 lines. Any 401 means the account or URL is wrong, so go back to step 3.

The fetcher client

# snap_restream.py - example client; the constants are initial settings, edit to fit
import os
import time
import requests
from requests.auth import HTTPDigestAuth  # documented class; pass auth=(user, pw) for Basic

SNAPSHOT_URL = "http://192.168.10.21/media/cam0/still.jpg?res=max"  # str: your still path
USER         = "snap-ro"                    # str: dedicated read-only camera account
PASSWORD     = os.environ["CAMERA_PASS"]    # str: secret from the environment, not the file
REFRESH_S    = 2.0                          # float: seconds between camera pulls
BIND_PORT    = 8081                         # int: port the HMI tile loads

http = requests.Session()  # reuses the TCP connection; credentials still travel on every request

def pull_jpeg() -> bytes:
    r = http.get(SNAPSHOT_URL, auth=HTTPDigestAuth(USER, PASSWORD), timeout=5)
    if r.status_code in (401, 403):
        raise PermissionError(f"HTTP {r.status_code}: credentials refused")
    if not r.content.startswith(b"\xff\xd8"):
        raise ValueError("reply is not a JPEG: login page, error page or wrong URL")
    return r.content

from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

class TileStream(BaseHTTPRequestHandler):
    def do_GET(self) -> None:
        self.send_response(200)
        self.send_header("Content-Type", "multipart/x-mixed-replace; boundary=jpg")
        self.end_headers()
        try:
            while True:
                jpg = pull_jpeg()
                self.wfile.write(b"--jpg\r\nContent-Type: image/jpeg\r\n\r\n" + jpg + b"\r\n")
                time.sleep(REFRESH_S)
        except (BrokenPipeError, ConnectionResetError):
            pass  # tile closed its side, normal on a page reload

ThreadingHTTPServer(("0.0.0.0", BIND_PORT), TileStream).serve_forever()

The constants at the top are the only edits: still path, account, refresh period, port. The JPEG signature check doubles as the session detector for form-login cameras — any reply that does not start with FF D8 is a login page. For those cameras add one branch: POST the login form once (its endpoint shows in the same network tab from step 1), keep the cookie jar, and on a bad signature re-login and retry once. Cap the retries at three and raise an alarm instead of looping forever, because some firmware temporarily locks an account after repeated failed logins.

Prove it before the tile exists: start the script, open http://192.168.10.50:8081/ in a browser and confirm a fresh frame every 2 s. Session independence is structural here — with challenge auth the client performs no login at all — so a weekend should pass with zero 401 lines and roughly 43,200 pulls.

The numbers: what a 1080p tile every 2 s costs

Weigh one frame first, from the fetcher log or the browser's network tab. A 1080p indoor scene typically lands between 150 and 300 KB as a JPEG; the arithmetic below uses 200 KB.

pull rate = 1 / refresh = 1 / 2 s = 0.5 pulls/s bandwidth = frame × pull rate = 200 KB × 0.5 pulls/s = 100 KB/s ≈ 0.8 Mbit/s daily volume = (86,400 s / 2 s) × 200 KB = 43,200 pulls × 200 KB ≈ 8.64 GB/day

On a 100 Mbit/s plant link that is 0.8% utilization; on gigabit it is noise. The host's CPU stays near idle because the bytes pass through untouched — no decode, no re-encode, just a signature check and a write. Viewers multiply only the local side: each tile reading the stream draws its own 0.8 Mbit/s from the fetcher while the camera side stays at one pull, so ten tiles press on one session instead of ten.

Latency equals the refresh period: a 2 s tile runs up to 2 s behind the scene. That is fine for presence checks, queue counts and dock doors, and wrong for judging motion — which is where the decision sits. At 2 s and slower (0.5 fps and below) the authenticated pull wins on simplicity. From about 1 fps upward, restream the camera's RTSP stream instead: a typical 1080p encoder holds 25-30 fps at 2-4 Mbit/s continuous, and the guide to restreaming a GigE Vision camera over RTSP covers the stream side.

Size the host by ports and duty cycle, not CPU; a 0.5 fps pass-through saturates nothing. The MV-VC2140-128G40-1T is the small option, with an Intel N-series N97 processor, 8 GB of memory, a 128 GB SSD plus a 1 TB HDD, and 3-4 LAN ports for the two network sides.

Vision controllersHikrobot MV-VC2140-128G40-1T VC2100 Vision Controller, N97, 8GB+128G SSD+ 1THDDMV-VC2140-128G40-1T
LAN ports
3-4 LAN ports
USB 3 ports
2 USB 3 ports
Light outputs
4 built-in channels
Memory
8 GB
$1,679.00View partBuild a system with it

The MV-VC3301H-128G66 steps up to an Intel Core i3-8100 with 8 GB of memory and a 128 GB SSD, 5+ LAN ports, PCIe expansion supported, no PoE, and a VM6200Pro license included.

Vision controllersHikrobot MV-VC3301H-128G66 I3-8100, 8GB+128G SSD, No PoE, PCIe expansion supported, Incl. VM6200Pro licenseMV-VC3301H-128G66
LAN ports
5+ LAN ports
USB 3 ports
3-4 USB 3 ports
Light outputs
4 built-in channels
Memory
8 GB
$2,419.00View partBuild a system with it

For a larger cell with PoE cameras, the MV-VC5525-256G70-NN pairs a 12th-gen Intel Core i5 with 32 GB of memory and a 256 GB SSD, 5+ LAN ports including PoE camera ports, 8 built-in light channels and PCIe expansion.

Vision controllersHikrobot MV-VC5525-256G70-NN VC5000 Vision Controller, 12th-I5, 32GB+256G SSD, Supported PCIEMV-VC5525-256G70-NN
LAN ports
5+ LAN ports
USB 3 ports
3-4 USB 3 ports
PoE
PoE camera ports
Light outputs
8 built-in channels
$4,479.00 · quotedView and quoteBuild a system with it

Pitfalls that break the restream later

Four failure modes account for nearly every relapse:

  • The shared account. Pointing the fetcher at the recorder's admin account puts it in competition with real viewers and presses the session cap. One read-only account per consumer keeps both the caps and the logs legible.
  • A pinned first frame. Some runtimes cache the image URL and never ask again. A rotating query parameter on the local URL (append ?t= with the current epoch at each refresh) forces the reload; for the stuck-socket side, the guide to restarting an MJPEG stream covers closing and cache busting.
  • A broken multipart stream. The boundary string in the Content-Type header and the per-frame image/jpeg type must both be exact, or the tile stays broken while the log looks fine. Stray bytes around the JPEG marker produce the corrupt-frame errors covered in the guide to extraneous bytes before the marker in MJPEG streams.
  • Retry without a ceiling. The re-login loop for form-login cameras must stop and alarm; repeated failed logins can lock the account on some firmware, and a locked account looks identical to an expired session while being much harder to clear.

When a snapshot restream is the wrong tool

Stills are the wrong medium the moment the operator needs motion or timing: a 2 s tile cannot show whether a part seated cleanly. For a live view, restream RTSP at the switch and keep the pull client only as a fallback — the guide to displaying an RTSP camera stream in an HMI compares the two paths. And if the tile's real job is an inspection result rather than pixels, move the inspection onto the camera: the smart cameras category is where that starts, and the guide to a smart camera image in a PLC HMI shows what actually displays in the tile.

Remote viewing changes the topology, not the rule. Keep the fetcher on the camera's subnet and let remote clients reach the fetcher over a routed VPN; the subnet-route steps in the guide to reaching a smart camera over VPN apply as-is. Never expose the camera's login page or the unauthenticated stream to a wide area network.

Unattended-run checklist

Before you walk away

  • Dedicated read-only account created; the admin account untouched.
  • Step 3 returned HTTP 200 and a frame starting FF D8.
  • The fetcher runs as a service that starts on power-up, not a terminal window someone will close.
  • The tile URL points at the fetcher's port, never at the camera.

Through the night

  • Roughly 43,200 pulls at the 2 s setting, from 86,400 s / 2 s.
  • Zero logins after startup and zero 401 or 403 lines in the log.

In the morning

  • The tile shows the current scene with no manual login.
  • Power-cycle the fetcher once and watch the tile recover by itself within one refresh period. That proves the service, not a person, owns the login — which is the whole point.

If you would rather not size the host yourself, send the camera count and refresh rate through the contact page and we will name the smallest controller that carries it.

Questions

How long does an IP camera login session last before it times out?

Whatever the camera's web service sets: embedded web UIs typically expire idle sessions within 5-30 minutes, and some firmwares add an absolute cap near a day, which is why tiles die by the next morning. The current value usually sits on the camera's web settings page under security or user management. Because it moves with firmware, the dependable design authenticates every request and leaves no session to time out.

Does embedding the user and password in the snapshot URL stop the timeout?

Credentials in the URL do authenticate every request, so no session can expire, but current browsers and many HMI image widgets strip userid:password@host from embedded image requests, so the tile fails for a different reason. Keep the trick on the server side: a fetcher sends the credentials per request and hands the tile a clean, locally served copy.

What refresh rate can a snapshot restream realistically keep?

At 1080p a still lands near 200 KB, so a 2 s refresh costs 0.8 Mbit/s and 8.64 GB per day; a 500 ms refresh quadruples that to about 3.2 Mbit/s and still looks like a slow slideshow. Past roughly 1 fps, switch to an RTSP restream: typical 1080p encoders hold 25-30 fps at 2-4 Mbit/s continuous.

Why does the camera send a login page instead of a 401 response?

Form-style web UIs answer an unauthenticated image request with HTTP 200 or a 302 redirect carrying HTML, so no request header can satisfy them directly. Challenge-style cameras reply 401 with a WWW-Authenticate header naming Basic or Digest, which per-request credentials do satisfy. The quick test is the first bytes of the reply: FF D8 means a JPEG arrived, anything else means the login page did.

Can the fetcher run on the HMI panel PC itself?

Yes, as long as that machine never sleeps or reboots for updates. In most cabinets the cleaner host is a fanless vision controller with one network port on the camera side and one on the automation side. Keep it off an engineer's workstation: the first patch restart takes the tile down with it, and an unattended restream is exactly what survives weekends.