Didier Deschamps’ side looked out of contention at the break after conceding two goals in the space of four minutes, but a thrilling second-half fightback set up a meeting with Spain.
AC Milan full-back Hernandez was the match-winner, collecting the ball on the edge of the box and firing home a finish into the far corner to spark joyous scenes at the Allianz Arena in Turin.
It started well for Belgium, who took a 37th-minute lead when Yannick Carrasco cut in from the left and drilled in a finish at the near post, wrongfooting goalkeeper Hugo Lloris.
Just four minutes later, Roberto Martinez’s side doubled their advantage as Romelu Lukaku latched on to Kevin de Bruyne’s through ball and fired a blistering shot into the roof of the net.
But Karim Benzema pulled a goal back on the hour mark, turning and sliding a finish into the bottom corner from Kylian Mbappe’s lay-off.
And they drew level through Mbappe’s confidently taken penalty after Youri Tielemans’ trip on Antoine Griezmann inside the area.
Lukaku thought he had won it with three minutes remaining after slotting in from Carrasco’s low cross, but the strike was ruled out by VAR for offside.
There was still time for Paul Pogba to rattled the crossbar with a sublime curling free-kick from 30 yards out, before Hernandez struck late on.
The final takes place at the San Siro on Sunday (19:45 BST kick-off), while the third-place play-off between France and European champions Italy is in Turin on the same day (14:00).
Discussion about this post