BysMax

Setting up media.path in Traccar: Saving and Serving Camera Photos

14 min

IMPORTANT

Configuring video for Traccar. The server tells the camera where to connect by reading web.url and the jt1078 / jimiphoto ports from your traccar.xml. If that's missing there's no live video and no alarm photos — and no error in the log either.

Reading order for this series: 1. how camera support works2. configure the server3. which camera to buy

If you already have a device with a compatible camera, enabling photo storage in Traccar is surprisingly simple: a single configuration key. What isn't so simple is everything that comes after — disk, permissions, and the proxy. Let's go through it piece by piece.

The real traccar.xml keys

There are only two keys related to multimedia. These:

<entry key='media.path'>/opt/traccar/media</entry>
<entry key='media.bufferSize'>33554432</entry>

media.path

This is the folder where the server stores audio, video, and photos. Its default value is ./media, relative to the server's working directory. Traccar automatically creates a subfolder per device using its unique identifier.

This key is also the switch. If media.path has a value, the servlet that serves the files gets registered; if not, it doesn't. Don't go looking for a media.enable key because it doesn't exist — it's a frequent mistake on the forum. Since the default value is already ./media, in practice the system is operational right out of the install; what's usually missing is pointing it to an absolute path with real space available.

media.bufferSize

The maximum size, in bytes, of a multimedia file a decoder can accumulate from a device. The default is 32 MB (33554432). Transfers that exceed that limit get discarded.

Two things to keep in mind:

  1. Only one buffer per connection is kept. Don't expect parallel transfers over the same socket.
  2. Cranking this value way up isn't free: it's memory the server reserves per active connection mid-transfer. If you're going to receive video clips from several vehicles at once, size your RAM accordingly.

For typical ADAS/DSM camera photos (100–500 KB) the default value is more than enough.

Where the files end up

The on-disk structure looks like this:

/opt/traccar/media/
├── 356938035643809/
│   ├── 1754400000000.jpg
│   └── 1754400600000.jpg
└── 862205059000000/
    └── 1754401200000.h265

One folder per uniqueId (the IMEI or identifier you registered), and inside it files named by timestamp. On the corresponding position, Traccar adds an image, video, or audio attribute whose value is just the file name, not the path.

How they're accessed from outside

Traccar exposes the folder at:

GET /api/media/{uniqueId}/{fileName}

For example:

curl -u user:password \
  https://your-server.com/api/media/356938035643809/1754400000000.jpg \
  -o photo.jpg

This is not a public folder. In front of it there's a filter that requires a valid session and checks that the authenticated user has permission over that specific device. A user on your platform can't pull another customer's photos by guessing IMEIs.

Disk space: the part that bites

Nobody plans for this and then the server runs out of disk three weeks later. Do the math beforehand:

  • A 300 KB event photo
  • Times 10 events a day
  • Times 50 vehicles
  • Times 30 days

4.5 GB a month. With video the figure easily balloons into tens of GB.

Two practical consequences:

  1. Mount media.path on a separate volume from the operating system. If it fills up, it shouldn't take down the database or the logs with it.

  2. Traccar's automatic cleanup of old positions does not delete the media files. If you purge the history, the JPGs are left orphaned on disk. You need your own cleanup job, for example:

    # Delete media files older than 90 days
    find /opt/traccar/media -type f -mtime +90 -delete
    

    Test it first without -delete and review the output before scheduling it in cron.

Permissions

The Traccar service needs write access to that folder. If you create it by hand as root, the service will fail silently on writes:

sudo mkdir -p /opt/traccar/media
sudo chown -R traccar:traccar /opt/traccar/media

Adjust the user to whatever your installation uses (in Docker it's usually different; there, what matters is mounting a persistent volume at the path you declare in media.path, or you'll lose your photos on every container recreation).

A note on the reverse proxy

If you have Nginx or Apache in front for HTTPS (and you should — see secure connection for Traccar), keep in mind that /api/media/ serves binary files that can be considerably heavier than a normal JSON API response.

If you see cut-off downloads or errors opening large video clips, check your proxy's response size limits and timeouts. In Nginx, the usual parameters to look at are proxy_read_timeout and response buffering.

And if you're also using JT1078 live video, there's a second path the proxy has to let through: /api/stream/, which serves the live.m3u8 playlist and the .ts segments. This is a classic failure: the video "won't start" when in fact the playlist loads fine but the segments 404 or get stuck in the proxy's buffering. I cover it in detail in JT1078 live video on Traccar.

NOTE

Traccar doesn't publish official proxy recommendations specific to media, so the above is general operating advice, not a configuration prescribed by the project. Start with the standard configuration and adjust only if you run into real failures.

If you have a JT808 MDVR, don't look for videos in media.path

A heads-up to save you hours of debugging: if your unit is an MDVR that speaks JT808/JT1078, you can configure media.path perfectly well and still not see a single video file there. That protocol doesn't use the media system: Traccar acts as a live relay and the recorded history stays on the vehicle's SD card.

What's normal on those servers is finding the folder with nothing but a device.jpg (the device's profile picture, which is a separate mechanism entirely). It isn't broken: it's the design. I explain it in live JT1078 video in Traccar. If you're choosing hardware, it's worth checking which cameras actually work with Traccar before you buy.

Configuring video for Traccar in traccar.xml

This is the part almost nobody documents: the 0x9101 command doesn't carry fixed values, it reads them from your configuration. If the XML is incomplete, Traccar still sends the command, logs no error at all, and the camera simply never shows up.

<entry key='web.url'>http://gps.yourdomain.com</entry>
<entry key='media.path'>/opt/traccar/media</entry>
<entry key='jt808.port'>5015</entry>
<entry key='jt1078.port'>5263</entry>
<entry key='jimiphoto.port'>5267</entry>

web.url — this is where the camera's target address comes from

When building videoStart, the JT808 encoder does URI.create(web.url).getHost() and writes that host into the frame. Translation: what the camera will try to resolve is the host in web.url.

Practical consequences:

  • If web.url isn't defined, the video command never gets built.
  • If it points at localhost or a private IP, the camera — sitting on a cellular network — will never reach it. It has to be a public host, resolvable from the vehicle's modem.
  • The http:// and the :8082 you put there are irrelevant to the camera: only the hostname is used.

jt1078.port — the port that travels inside the command

The port Traccar writes into the 0x9101 is neither the web port nor the JT808 one: it's the jt1078 port, read from the configuration using the protocol prefix. Default is 5263.

And there's a detail here that breaks whole installations: a protocol only starts if its port is greater than zero. If you also declared protocols.enable, that key behaves as an allow-list — if jt1078 isn't in it, the port never listens and the camera ends up connecting to a closed port.

jimiphoto.port — alarm media upload

When an alarm with an attachment arrives, the JT808 decoder sends the camera an upload instruction shaped like this:

VIDEOUPLOAD,<host from web.url>,<jimiphoto.port>,<alarm label>,1-2-3,2

If web.url is empty or the jimiphoto port is zero, that function is skipped silently: nothing in the log, the attachment is simply never requested. It's the number-one cause of the classic "I get the alarm but never the photo".

<protocol>.address — watch the bind

There's a per-protocol .address suffix (jimiphoto.address, jt1078.address…) that defines which network interface that service listens on. If you don't declare it, it listens on all of them.

Setting it to 127.0.0.1 restricts the service to loopback, and then a camera out on the internet can't upload anything. It only makes sense if there's a proxy on the same machine in front of it. If in doubt, don't declare the key.

And a key that doesn't exist: media.server

Forums — and AI answers — pass around the idea of configuring something like this:

<!-- this does absolutely nothing -->
<entry key='media.server'>SERVER,2,gps.yourdomain.com,5263,0#</entry>

media.server does not exist in Traccar's code. That SERVER,2,...# is a device command for Jimi/Concox units to point them at a server, not a configuration key: harmless in the XML, but completely ignored. The address the camera receives comes from web.url, as explained above.

Firewall and Docker

All three ports have to be open inbound:

PortService
5015JT808 — telemetry and commands
5263JT1078 — live video stream
5267JimiPhoto — alarm media upload

If the server runs in Docker you also have to publish them explicitly and mount media.path on a volume; otherwise you lose the files every time the container is recreated.

WARNING

Never publish or share your traccar.xml as-is: it carries your database credentials and, if you use notification webhooks, their private URLs. When asking for help, share only the relevant keys with placeholder values.

Verifying it works

  1. Confirm your device uses a protocol with real media support — the list is short and I go through it in how the media system works.

  2. Trigger a photo (via command, or by causing the event that generates one).

  3. Check the folder:

    ls -la /opt/traccar/media/YOUR_IMEI/
    
  4. If the file is on disk but you don't see it in the interface, the problem is user permissions on the device or the proxy — not the decoder.

  5. If there's no file, check the server log: it's almost always that the protocol doesn't implement media, or that the transfer exceeded media.bufferSize.

Comentarios (0)