Your general workflow should work as shown in this small example:
# Setup
model = models.resnet18()
data = torch.randn(1, 3, 224, 224)
target = torch.randint(0, 1000, (1,))
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=1e-3)

# Train
optimizer.zero_grad()
out = model(data)
loss = criterion(out, target)
loss.backward()
optimizer.step()

# Freeze all but last layer
for name, param in model.named_parameters():
    if not 'fc' in name:
        param.requires_grad = False
      
optimizer.zero_grad()
out = model(data)
loss = criterion(out, target)
loss.backward()

# Check grads
grad_frozen = model.conv1.weight.grad
grad_fc = model.fc.weight.grad
print(grad_frozen.abs().sum())
print(grad_fc.abs().sum())

optimizer.step()

As you can see, after freezing all parameters but the fc.weight and fc.bias, I get a zero gradient for a previous layer, while I get valid gradients for the last linear layer.
Which layers are you checking and are you sure the checked frozen layer is in model.fc_layers?