Substitute Magomed Ozdoev scored a stunning half-volley from the edge of the box after Chelsea had come from behind to lead 3-2 with minutes left.
Timo Werner scored in both halves and also set up Romelu Lukaku after Zenit had come from 1-0 down to lead 2-1.
But Ozdoev’s strike meant Chelsea finish behind Juventus, who beat Malmo.
Thomas Tuchel’s side – whose only defeat of the group stages came in Turin when they lost 1-0 to Juventus – made the perfect start, Werner reacting to poke in the opener within three minutes.
Two goals in quick succession from Claudinho and Sardar Azmoun saw Zenit go in front at the break, before Werner’s one-two with Ross Barkley helped tee up Lukaku for Chelsea’s second.
Werner, who was a threat for Chelsea throughout, showed composure to make it 3-2 when substitute Christian Pulisic played him in with five minutes remaining.
But Ozdoev’s stunning stoppage-time strike ensured Juventus topped Group H, while Zenit finished in third and will go into the Europa League play-offs in February.
Chelsea troubled by Zenit
Tuchel made eight changes for the game in Russia, saying fresh legs were needed to manage a heavy fixtures schedule in December.
Lukaku, returning to the side for the first time since October after injury, lacked sharpness but was in the right place to tap in Werner’s cut-back in the second half.
Werner thought he had given Chelsea victory when he slotted in his second late on – anxiously awaiting a check from the video assistant referee for a potential offside.
But Zenit, who had threatened Chelsea on numerous occasions in the first half, remained a threat deep into stoppage time.
Earlier misses from Mason Mount, Werner and Saul will add to Chelsea’s disappointment but Zenit also had chances of their own – Azmoun was denied in a one-on-one with Kepa Arrizabalaga in the first half and again from a header in the second.
Chelsea end the group stage with four wins from six games, two points off Juventus.