Basically, cherry-pick, or merge - depending on what you have in those branches, and what you want to end up with, and how you are using git to aid in your work.
Assuming that your code is "clean", then, first, consider merge from prod to main. This has the benefit of ensuring that no change made on prod branch will be ever accidentaly forgotten. This means that all future 'sync ups' in direction from:prod->to:main can be done the same way - just by merge. This also has the benefit that if you look at the commit graph, you can easily see if there are any fixes on prod that have not been synced-up yet. I like to call that back-merge.
However, this has limitations. I suppose you understand all the implications of a merge. From that, come the restrictions, and what I mean by "clean".
If you keep on prod branch anything that is not good for main branch, then you probably should not do plain merge. This usually includes, for example environment-specific things like passwords, keys, service urls, branding resources, etc. Aside from such resources, this may also mean any hotfixes that you actually don't want to port across branches (however, for those, I'd suggest merging them anyways, and then deleting/reverting/undoing them explicitly on main branch, so there's a clean indication in the commit history that (1) they were considered, and (2) it was decided they should no longer stay in newer versions).
Env-related resources do not always block back-merge workflow. This depends completly on how you manage those env-related things (if you have them at all). It may be safe to merge across prod/main, for example, if you keep all of those in separate files (env.devel.config, env.prod.config and so on) so they can't ever accidentaly overwrite each other during a merge.
If you don't know if you have anything like that in your codebase, just try doing such prod->main merge locally, do not push it anywhere, and carefully analyze the result of the merge (merge commit diff - see what this commit has changed on main). If you see anything unwanted flowing into main, just abort/reset/etc the merge.
Now, if for some reason merge is not viable, then do a cherry-pick like Asad suggested in comments.
Cherry-pick has the benefit that it will not automatically pick any commits that you don't explicitly ask for. Cherry-pick by default will copy a single commit (or range) between branches, and will literally COPY it. For git, this will be a brand new change on main, just incidentally moreless identical to that old change from prod branch. The -x option in cherry-pick only leaves a message for humans, so you can track cherry-picks manually if you need, and git doesn't use this message for anything. Cherry-picking across branches is very useful sometimes, but when done often, it quickly gets worse as numbers of copied commits rise. Finding out that a commit was in fact copied from elsewhere is easy thanks to the commit message generated by -x, but tracing it the other way around - to find out where things were copied to, or what things were not yet copied - can be a major pain.
Of course, there's a third way. Neither merge, nor cherry-pick. You can manually re-do the change on main. This would be a non-automated, non-git-assisted cherry-pick. Sometimes it's required when for some reason cherry-pick results in horribly broken conflicts.
EDIT: you may find some advices re merge with squash. Careful with that. Despite being called 'merge', it's basically take-them-all cherry-pick. It doesn't leave a 'merge commit', and won't leave traceback parents 'lines' like a normal merge.
EDIT: as always, if someone says merge, another one will say rebase. Porting patches from prod isn't a good case for rebase, but anyways - rebase is basically an automated serial cherry-pick. It has many quirks, and is very powerful, but easy to shoot yourself in the foot. I always advise to add -i (interactive) to any rebase.
EDIT: you could also solve this relatively safely with a kind of feature branches. Like: take the patch out of prod. Find common base between prod and main. Create a patch branch in this place. Put the patch on that patch branch. Merge patch branch into prod, merge patch branch into main. This however on some occasions may end up in:
- huge wide tree, if you have lots of such pathes, and if you create new branches for each one (i.e. if you cannot piggy-back a new patch on an old patch branch for some reason)
- ugly conflicts (if common base is far enough in the history, and the code changed a lot since then)
- etc
soo.. a wall of text, but as always, YMMV, HtH, GL!