You have a few options, but it all depends on how many choices you are giving the user to select from and how much recall they have (have they seen these options previously? Are they familiar options? Are they new to the user?). Here are some options that you may want to consider:

If you don’t have too many options to select from, then you may wish to consider using 2 select options. However, whatever choice has been made in one option will need to be disabled in the other. The downside of this, there is no immediate visibility of the options available.

You could use a select from many list. This can be limited by height, where vertical scroll will appear if the options are greater than the container height. Choices may then be added to the selection on the right-hand-side. Only use this if you have up to approximately 15 choices (scrolling through choices will be annoying). The downside of this, you are still able to select more than 2 options. I would allow this and add error correction to your model:

This allows the user to experiment, make choices and refine selection within your constraints.
Last of all, the solution that you have eluded to. Once 2 checkboxes have been checked, disable all other options. I would use this option if you have a lot of choices to choose from and the user needs to scan through all of the options and make a choice. If you do have a lot of options like this, consider whether the options can be categorised. If they can, split them up under category headings to improve scan-ability. The downside of this, users must deselect an option before they can refine their choice when 2 options have already been selected.
To answer your question on “wait until after submission to alert” or “alert in place and double check upon submission” — I think it would be nicer to have inline validation, so once more than 2 choices are made, the user is alerted and made aware that only 2 choices can be made. If we take my last example, and just allow users to select as many as they like, this removes the downside I mentioned which is arguably less frustrating. I would definitely add inline validation if you go with this option.