There is no easy way to get updates from upstream while maintaining local changes to the code base. At some point, there will likely be conflicts, and you will need to resolve them manually.
Rebasing your changes onto the current state of upstream is not a viable solution in the long term, especially if your changes are more than a handful of commits. Since your adaptions are never merged back into the upstream repository, each time you rebase you will have to rebase everything you've ever done. That means resolving the same conflicts again and again. Rebasing also “rewrites” history which means that merging your branch becomes absolutely impossible.
If you need to keep your current workflow, it might be better to stop your main branch from tracking any upstream branch, and handle all merges manually – so effectively the commands you are suggesting. A
git pull upstream master is almost the same as
git fetch upstream; git merge upstream/master. Doing the merge manually will give you more control. Merging instead of rebasing also reduces your work load in the long run.
However, Git is not a suitable tool to manage different modifications of a code base. The term “version control” is slightly misleading here. This is not because Git is a bad tool, but because this is an inherently difficult problem. This problem becomes a lot easier if the original code base is sufficiently configurable so that you don't have to edit the code in order to implement your changes – in OOP speak, it should obey the open/closed principle.
While your problem-specific adaptions do not have any value for the original project, making it more configurable certainly would. The best strategy in the long term is therefore to make small changes to the original code to make it more configurable. This could mean setting some options in a configuration file, adding a plugin system, hooks and callbacks for certain events, or introducing dependency injection. Once those changes are in the upstream project, you can implement your changes without having to touch the upstream code, which significantly reduces your maintenance burden. Your problem-specific plugins would live in a completely separate project.
If the upstream project does not accept your modifications, maintaining a small set of patches as a compatibility layer is still better than making all your changes directly in the upstream code base.