Tomas Plekanec and Michael Ryder also scored for the Canadiens (39-31-6), who won their season-high sixth straight at home in front of a sellout crowd of 21,273 at the Bell Centre.
Chris Clark scored his 29th of the season for the Capitals (26-37-13), who lost their third straight to close out a three-game road trip.
The Canadiens kept pace with the Carolina for the eighth playoff spot in the tight Eastern Conference playoff race. The Hurricanes were 6-4 winners over San Jose. The New York Rangers, New York Islanders and Toronto Maple Leafs were also victorious Saturday. Montreal remained one point back of the seventh-place Rangers while maintaining a one-point lead over the Islanders and a two-point lead over Toronto.
Halak won his fourth straight game since being re-inserted as the starter in a 3-2 shootout win over the Maple Leafs on March 17. He's allowed only six goals on 132 shots for a .955 save percentage over that span.
Olaf Kolzig made 36 stops for the Capitals and was the main reason the score was so close. Kovalev's last two-goal game was in Game 1 of the Canadiens first-round playoff loss to Carolina.
Andrei Kostitsyn's assist on the Plekanec goal extended his point streak to four games and gave him one goal and six assists in his last six games.
The Capitals jumped out to a 1-0 lead on the power play at 5:53 of the first when Clark tipped an Alexander Semin wrist shot from the high slot into the top corner. But the Canadiens took over from there, peppering Kolzig with 34 shots through the first two periods.
Kovalev tied it at 8:59 of the first on quick wraparound, and then put Montreal up 2-1 with a power play goal at 9:36 of the second when he went to the net and tapped in a Saku Koivu rebound for his 16th goal of the season.
Plekanec made it 3-1 at 15:04 of the second with a top corner wrist shot for his 19th of the season. Ryder scored his team-leading 25th of the season at 14:50 of the third, keeping it on a 2-on-1 break and beating Kolzig with a wrist shot upstairs.
Notes: Canadiens D Janne Niinimaa was a healthy scratch Saturday after playing in the previous five straight games, his longest stretch since November. Niinimaa played sparingly in the third period of
Thursday's come from behind 6-3 win over the Bruins in Boston, and finally lost his spot to Josh Gorges on Saturday. Winger Sergei Samsonov was also a healthy scratch for a seventh straight game. . . . Canadiens D Sheldon Souray played his 500th career game.