Hikvision camera behind Caddy reverse proxy

I’ve already used Caddy successfully to securely reverse proxy various services in my home network. Now, however, I seem to be hitting a wall with a Hikvision network camera.

Reverse proxying the camera’s web interface is not an issue. The problem I’ve run into is that the live video feed is failing if the Hikvision camera is behind Caddy.

From what I’ve seen in Firefox’ dev tools and gleaned from this blog post, the Hikvision web client tries to establish a websocket connection (ws://) on port 7681 to the camera. If the camera is behind Caddy’s automatic HTTPS, the client is clever enough to switch to encrypted wss:// (on port 7682).

What I’m trying to do:

Have Caddy proxy wss://caddy:7682 to ws://camera:7681.

To that end, I’ve tried the following configuration in Caddyfile:

# Hikvision web interface
hikvision.{$MY_DOMAIN} {
        reverse_proxy hikvision-int.{$MY_DOMAIN}
        tls {
                dns cloudflare {env.CLOUDFLARE_API_TOKEN}
        }
}

# Hikvision web socket
https://hikvision.{$MY_DOMAIN}:7682 {
        reverse_proxy http://hikvision-int.{$MY_DOMAIN}:7681  
        tls {
                dns cloudflare {env.CLOUDFLARE_API_TOKEN}
        }
}

The first part works fine; the second (web socket) half is what I’m having trouble with.

Specifically, Caddy reports HTTP status 502 (error EOF) with the following log message (debug logging enabled):

{
  "level": "debug",
  "ts": 1739399179.532614,
  "logger": "http.handlers.reverse_proxy",
  "msg": "upstream roundtrip",
  "upstream": "hikvision-int.MYDOMAIN:7681",
  "duration": 0.003557717,
  "request": {
    "remote_ip": "10.0.9.96",
    "remote_port": "55549",
    "client_ip": "10.0.9.96",
    "proto": "HTTP/1.1",
    "method": "GET",
    "host": "hikvision.MYDOMAIN:7682",
    "uri": "/?version=0.1&cipherSuites=0&token=SOMETOKENVALUE",
    "headers": {
      "Sec-Fetch-Site": [
        "same-site"
      ],
      "Sec-Websocket-Key": [
        "B+0dGhT+rEhewWCKPJzEcQ=="
      ],
      "Sec-Websocket-Version": [
        "13"
      ],
      "Sec-Websocket-Extensions": [
        "permessage-deflate"
      ],
      "Accept": [
        "*/*"
      ],
      "Accept-Encoding": [
        "gzip, deflate, br, zstd"
      ],
      "Accept-Language": [
        "de,en-US;q=0.7,en;q=0.3"
      ],
      "Pragma": [
        "no-cache"
      ],
      "User-Agent": [
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:135.0) Gecko/20100101 Firefox/135.0"
      ],
      "Origin": [
        "https://hikvision.MYDOMAIN"
      ],
      "Cookie": [
        "REDACTED"
      ],
      "Sec-Fetch-Mode": [
        "websocket"
      ],
      "Connection": [
        "Upgrade"
      ],
      "Sec-Fetch-Dest": [
        "empty"
      ],
      "Upgrade": [
        "websocket"
      ],
      "Cache-Control": [
        "no-cache"
      ]
    },
    "tls": {
      "resumed": false,
      "version": 772,
      "cipher_suite": 4865,
      "proto": "http/1.1",
      "server_name": "hikvision.MYDOMAIN"
    }
  },
  "error": "EOF"
}

I’ve tried to access the camera’s ws:// URL with curl on the Docker host Caddy is running on. That seems to be working fine:

curl -v "hikvision-int.MYDOMAIN:7681/?version=0.1&cipherSuites=0&token=SOMETOKENVALUE"
*   Trying 10.0.11.21:7681...
* Connected to hikvision-int.MYDOMAIN (10.0.11.21) port 7681 (#0)
> GET /?version=0.1&cipherSuites=0&token=SOMETOKENVALUE HTTP/1.1
> Host: hikvision-int.MYDOMAIN:7681
> User-Agent: curl/7.88.1
> Accept: */*

I’d change your Caddyfile to:

# Hikvision web interface
hikvision.{$MY_DOMAIN} {
        reverse_proxy hikvision-int.{$MY_DOMAIN}
        tls {
                dns cloudflare {env.CLOUDFLARE_API_TOKEN}
        }
}

# Hikvision web socket
hikvision.{$MY_DOMAIN}:7682 {
        reverse_proxy http://hikvision-int.{$MY_DOMAIN}:7681  
        tls {
                dns cloudflare {env.CLOUDFLARE_API_TOKEN}
        }
}

Web proxying should just work. My understanding, since this is a websocket (ws://), specifying https:// would be wrong, so you wouldn’t want to prefix with https in the site block.

I had placed the https:// in front of the domain only for clarity. Removing it doesn’t change anything.

# Hikvision web interface
hikvision.{$MY_DOMAIN} {
        reverse_proxy hikvision-int.{$MY_DOMAIN}
        tls {
                dns cloudflare {env.CLOUDFLARE_API_TOKEN}
        }
}

# Hikvision web socket
https://hikvision.{$MY_DOMAIN}:7682 {
	@websockets {
		header Connection *Upgrade*
		header Upgrade    websocket
	}
        reverse_proxy @websockets http://hikvision-int.{$MY_DOMAIN}:7681  
        tls {
                dns cloudflare {env.CLOUDFLARE_API_TOKEN}
        }
}

I think specifying header Upgrade websocket will correct the issue.

Unfortunately, that doesn’t change anything. The issue persists.

Are your Caddy logs still showing the same output?

Yes, nothing changed.

I’ve been thinking about this for a while, now. Are there any specific manuals or Wikis listing how exactly Hikvision is handling both the interface and video feed?

I’m not aware of any docs except the blog article I linked to in my original post.

I found this hardening guide for Hikvision’s HikCentral Professional software. Is the camera you’re trying to connect feed to called an encoding device? If so, do you have the IP/domain, device port, and mapped port configured correctly?

Edit: Here’s the page on configuring everything. On the section “Add Streaming Server”, is your configuration reflected accurately to what is posted here?

The links you posted point to docs for the software HikCentral Professional. It seems to be some kind of management software for HikVision cameras.

I don’t have the software; I just have a camera and I’m trying to access the camera’s web interface via Caddy as a reverse proxy.

As far as I can tell, it has to be programmed by you. There would be no other way to know what the IP or ports are. If you know for sure that the port is 7681, I’d remove :7682 from your site block.

@TheRettom I don’t want to be impolite, but your answers are not helpful. Please refrain from posting additional comments in this topic.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.