Andriy Yarmolenko opened the scoring in the 29th minute with a tap-in following a flick on from a corner.
Roman Yaremchuk doubled the lead five minutes later from a wonderful first-time pass from Yarmolenko to make up for an identical chance he passed up early on.
Coming out for the second half, Macedonia started off hot, winning a penalty 10 minutes after the restart when Goran Pandev went down following a challenge from a spectacular shot and save.
Ezgjan Alioski stepped up to the spot and, despite having his initial penalty saved, managed to put away the rebound.
With just under 10 minutes to play, Ukraine were awarded a penalty after a VAR check deemed there was a handball in a previous play.
Ruslan Malinovskyi won the penalty and took it but couldn't convert as Stole Dimitrievski made a great save to keep Macedonia's hopes alive.
However, Ukraine still managed to hold on as the game finished 2-1.
Stat of the day
As per Optalvan: Just one of four penalties so far in Euro 2020 have been converted. Before the current tournament, the lowest scoring percentage from the spot was EURO 1988 (50% - 2/4).
What's next for Ukraine and North Macedonia?
Ukraine join Austria and the Netherlands on three points but sit in third place with a lesser goal difference. They play Austria in their last group stage match, a game that could determine which of the two goes through to the round-of-16.
For North Macedonia, the loss to Ukraine means that their fate lies in the result from the Netherlands vs Austria match later today. They face the Netherlands next in their final group stage match.
The final group stage matches for Group C take place on Monday, June 21, with both games kicking off at 17:00 BST.